diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..06fd1f24 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,28 @@ +# Buridan UI Expert Rules + +You are an expert at Buridan UI and Reflex. When building applications or helping users with this design system, follow these strict rules: + +## 1. Component Management +- **Never copy-paste component code manually.** +- **Always use the CLI**: Run `buridan add ` to add components. +- This ensures dependencies like `twmerge`, `CoreComponent`, and icon providers are correctly included. + +## 2. Project Structure +- Components MUST be imported from the `components/` directory in the user's project root. +- Icons: `from components.icons.hugeicon import hi`. +- UI: `from components.ui.button import button`. + +## 3. Theming +- Buridan UI uses OKLCH variables defined in `assets/globals.css`. +- Use semantic Tailwind classes: `bg-background`, `text-primary`, `text-muted-foreground`. +- To change themes, use `buridan init --preset `. + +## 4. Architectural Patterns +- **Layouts**: Use the `@layout_decorator` from `app.templates.layout` to wrap pages. +- **State**: Use `reflex.experimental.ClientStateVar` for client-side interactions. +- **Consistency**: Maintain the masonry grid layout style for dashboards (`columns-[320px]`). + +## 5. Helpful Commands +- `buridan list`: See available components. +- `buridan add button input badge`: Add multiple components. +- `buridan init --preset b0`: Initialize with a theme. diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 27fbc987..d927e3f4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,33 +1,33 @@ -name: Deploy Reflex App +# name: Deploy Reflex App -on: - push: - branches: - - main +# on: +# push: +# branches: +# - main -jobs: - deploy: - name: Deploy to Reflex Cloud - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 +# jobs: +# deploy: +# name: Deploy to Reflex Cloud +# runs-on: ubuntu-latest +# steps: +# - name: Checkout code +# uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.12" +# - name: Set up Python +# uses: actions/setup-python@v4 +# with: +# python-version: "3.12" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install uv - uv pip install --system -e . +# - name: Install dependencies +# run: | +# python -m pip install --upgrade pip +# pip install uv +# uv pip install --system -e . - - name: Generate Markdown Files - run: python -m scripts.generate_markdown +# - name: Generate Markdown Files +# run: python -m scripts.generate_markdown - - name: Deploy to Reflex - id: deploy - run: | - reflex deploy --app-name ui --project ${{ secrets.REFLEX_PROJECT_ID }} --token ${{ secrets.REFLEX_AUTH_TOKEN }} --no-interactive +# - name: Deploy to Reflex +# id: deploy +# run: | +# reflex deploy --app-name ui --project ${{ secrets.REFLEX_PROJECT_ID }} --token ${{ secrets.REFLEX_AUTH_TOKEN }} --no-interactive diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 08d09fa9..0675794a 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -1,42 +1,42 @@ -name: Development Preview - -on: - pull_request: - types: [opened, synchronize, reopened] - branches: - - main - -jobs: - preview: - name: Run Dev Preview - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.x" - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install uv - uv pip install --system -e . - - - name: Generate Markdown Files - run: | - python -m scripts.generate_markdown - - - name: Run Reflex App (prod env) - run: | - nohup reflex run --env prod > reflex.log 2>&1 & - sleep 15 - echo "Reflex app started for PR #${{ github.event.pull_request.number }}" - - - name: Deploy to UI-Dev - id: deploy - run: | - reflex deploy --app-name ui-dev --project ${{ secrets.REFLEX_PROJECT_ID }} --token ${{ secrets.REFLEX_AUTH_TOKEN }} --no-interactive +# name: Development Preview + +# on: +# pull_request: +# types: [opened, synchronize, reopened] +# branches: +# - main + +# jobs: +# preview: +# name: Run Dev Preview +# runs-on: ubuntu-latest + +# steps: +# - name: Checkout code +# uses: actions/checkout@v3 + +# - name: Set up Python +# uses: actions/setup-python@v4 +# with: +# python-version: "3.x" + +# - name: Install dependencies +# run: | +# python -m pip install --upgrade pip +# pip install uv +# uv pip install --system -e . + +# - name: Generate Markdown Files +# run: | +# python -m scripts.generate_markdown + +# - name: Run Reflex App (prod env) +# run: | +# nohup reflex run --env prod > reflex.log 2>&1 & +# sleep 15 +# echo "Reflex app started for PR #${{ github.event.pull_request.number }}" + +# - name: Deploy to UI-Dev +# id: deploy +# run: | +# reflex deploy --app-name ui-dev --project ${{ secrets.REFLEX_PROJECT_ID }} --token ${{ secrets.REFLEX_AUTH_TOKEN }} --no-interactive diff --git a/.gitignore b/.gitignore index e02731e5..d3ef20ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,16 @@ .states -*.db +.web +assets/external/ *.py[cod] +*.db .DS_Store -.idea -.venv -.web +# Python-generated files __pycache__/ -assets/external/ -.vercel +*.py[oc] +build/ dist/ -buridan_ui.egg-info -.reflex_ui_cache/ -src/components/ -assets/docs/-components/ +wheels/ +*.egg-info + +# Virtual environments +.venv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 3ce930bd..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,8 +0,0 @@ -repos: - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.11.2 - hooks: - - id: ruff-format # Ruff will format first - args: [] - - id: ruff # Then Ruff lints, fixing what it can - args: ["--fix", "--exit-non-zero-on-fix"] diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..2c073331 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ce7e2d2..e69de29b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,239 +0,0 @@ -# Site Updates & UI Refinements -**Date:** October 1, 2025 -**Version:** buridan/ui v0.7.3-beta - -- Updated **site documentation** and released changes. [#55](https://github.com/buridan-ui/ui/pull/55) -- Bumped dependency to `reflex==0.8.9` and updated `uv.lock`. [#56](https://github.com/buridan-ui/ui/pull/56) -- Applied several **UI patches** across components. [#57](https://github.com/buridan-ui/ui/pull/57) -- Refined **footer layout** and additional UI adjustments. [#58](https://github.com/buridan-ui/ui/pull/58) -- Added **Korean export documentation** and fixed typos. [#59](https://github.com/buridan-ui/ui/pull/59) -- Performed broader **site UI refactoring** for consistency. [#60](https://github.com/buridan-ui/ui/pull/60) - -# Updates & Site Enhancements -**Date:** August 31, 2025 -**Version:** buridan/ui v0.7.2 - -- Introduced new Pantry component: **Timeline v2**. [#50](https://github.com/buridan-ui/ui/pull/50) -- Added updated & new **Pricing components** for expanded options. [#51](https://github.com/buridan-ui/ui/pull/51) -- Renamed project structure folder from `source` to `src`. [#52](https://github.com/buridan-ui/ui/pull/52) -- Performed general **chore & cleanup** tasks. [#53](https://github.com/buridan-ui/ui/pull/53) -- Released a **minor patch to the landing page**. [#54](https://github.com/buridan-ui/ui/pull/54) - -# Updates & Site Enhancements -**Date:** August 3, 2025 -**Version:** buridan/ui v0.7.1 - -- Updated Reflex version and configuration file for latest compatibility. [#47](https://github.com/buridan-ui/ui/pull/47) -- Integrated GoatTracker for privacy-friendly site analytics. [#46](https://github.com/buridan-ui/ui/pull/46) -- Added Reflex Build link to the site TOC for easier access to the AI builder. [#49](https://github.com/buridan-ui/ui/pull/49) -- General code cleanup and maintenance improvements. [#48](https://github.com/buridan-ui/ui/pull/48) - -# Search Feature For Buridan UI Site -**Date:** July 13, 2025 -**Version:** buridan/ui v0.7.0-beta.2 - -- Added search functionality to the Buridan UI site for enhanced user experience. -- Added a fast, client-side search function using ClientStateVar for ultra-responsive results. -- This ensures fast, real-time search results with no server-side delay, improving user experience. - -# Major API Revamp & Foundation for v0.7.0-beta.1 Release -**Date:** July 6, 2025 -**Version:** buridan/ui v0.7.0-beta.1 - -- Introduced brand new fluent APIs for core chart types: AreaChart, BarChart, LineChart, and PieChart. -- Redesigned internal charting references and hooks to improve extensibility and performance. -- Implemented breaking changes to component refs to simplify and standardize usage. -- Updated all chart components to support custom legends, dynamic series, and enhanced theming. -- Refined the documentation to reflect the new APIs and usage patterns. -- Upgraded build and deployment scripts to streamline beta releases. -- This beta version signals that v0.7.0 is feature-complete and ready for broader testing and feedback. - -# Enhanced Charting Experience & Theming Overhaul -**Date:** June 1, 2025 -**Version:** buridan/ui v0.6.7 - -- Introduced a centralized theming system for consistent chart styling. -- Enhanced Recharts with smooth animations and improved responsiveness. -- Added subtle hover effects to theme selection for better UX. -- Updated documentation for theming and charting components. - -# Minor Updates and Docs Enhancements -**Date:** May 30, 2025 -**Version:** buridan/ui v0.6.6 - -- Patch error for sidebar visibility feature. -- Support for uv package manager. -- Client State Var documentation. -- Dashboard documentation. - -# Sidebar Changes and Doc Improvement -**Date:** May 22, 2025 -**Version:** buridan/ui v0.6.5 - -- New changelog UI design page. -- Update sidebar with new visibility feature. -- Improved charting walkthrough documentation. - -# New Sidebar UX and Menu Structure -**Date:** May 18, 2025 -**Version:** buridan/ui v0.6.4 - -- New component wrapper menu bar. -- Updated chart structure: Area, Bar, Line. -- Bumped Reflex version and related files. -- New UX for sidebar. - -# Component Refinements and Bug Fixes -**Date:** May 04, 2025 -**Version:** buridan/ui v0.6.3 - -- Fixed issues in several components to enhance stability. -- Restored missing exports.py file. -- Performed code cleanup for improved maintainability. - -# New Features, Pro Tier, and Improvements -**Date:** April 20, 2025 -**Version:** buridan/ui v0.6.2 - -- Introduced Buridan Pro — gated access to premium components. -- Added development helper script: dev.sh for local testing & filtering. -- Implemented active sidebar highlighting based on current route. -- Improved layout: breadcrumb in main content, version in left sidebar. -- Right sidebar now includes a persistent header and optional callouts. -- Refined styling and responsiveness across components. -- Minor bug fixes and performance improvements. - -# Site Refactoring & Major Changes -**Date:** March 22, 2025 -**Version:** buridan/ui v0.6.1 - -- Major site refactoring of the codebase. -- Major updates to site UI & UX. -- Several sections have been removed or added elsewhere. -- Landing page has been removed. -- New landing page routes to either UI or Lab apps. -- New charts: Doughnut and Scatter Charts. -- New feature: Download repo directly from the site. -- New feature: Code view now separate from tab. -- Site is almost completely stateless (aim to make site static in the near future). - -# Buridan Dev Labs: Charts -**Date:** December 17, 2024 -**Version:** buridan/ui v0.4.2 - -- New `Dev Lab` for charts. Easily generate charts with your own data. -- Updated charts theme colors. -- Fixed Area Charts stacking options. - -# Site Patches and Updates -**Date:** December 13, 2024 -**Version:** buridan/ui v0.4.1 - -- Added GitHub workflow for Reflex Cloud Deploy automation. -- Code base cleanup and code refactoring. -- Live code editor (experimental) has been deployed (buridan-ui.reflex.run/buridan-sandbox). - -# Buridan Charts -**Date:** December 02, 2024 -**Version:** buridan/ui v0.4.0 - -- New charts landing page. -- New chart item: Radar Charts. -- New chart theme color: purple. -- New chart tooltip style sheet. -- Updated responsive logic for mobile view. -- Significant UI update to entire chart codebase. -- New dynamic charting for area, bar, and line charts. - -# Site patches and New Blueprint Items -**Date:** November 26, 2024 -**Version:** buridan/ui v0.3.4 - -- New blueprint items: Dashboards & Layouts. -- Major code refactoring for pantry, charts, and blueprint wrappers. - -# New library feature: Blueprint Templates -**Date:** November 20, 2024 -**Version:** buridan/ui v0.3.3 - -- Blueprints templates consist of in-depth, more well-rounded apps that can be used out of the box with minor changes. -- Authentication - -# New Site Landing Page and UI Changes -**Date:** November 17, 2024 -**Version:** buridan/ui v0.3.2 - -- New site landing page with animation! -- Fixed UI scaling issue for site: functional. -- Updated many site components (nav, side menu, etc...) - -# Small Patch for Site Scaling UI -**Date:** November 15, 2024 -**Version:** buridan/ui v0.3.1 - -- Fixed UI scaling issue for site: operational. - -# New Apps and Site UI Changes -**Date:** November 13, 2024 -**Version:** buridan/ui v0.3.0 - -- New pantry: Footers! -- New chart item: Pie Charts! -- New interactive app: PubMed A.I. -- UI changes to site landing page. - -# Site Refinement and UI Updates -**Date:** November 08, 2024 -**Version:** buridan/ui v0.2.0 - -- Changes to Charts component wrapper. -- Codebase refactor and state changes. -- Changes to code block theme and font size. -- Major changes to @DEMO_AND_SINGLE_FUNCTION menu items. - -# New Components and Improvements to Pantry Items -**Date:** October 21, 2024 -**Version:** buridan/ui v0.1.0 - -- Accordions -- Animations -- Backgrounds -- Cards -- Descriptive Lists -- Featured -- Footers -- Frequently Asked Questions -- Inputs -- Logins -- Menus -- Onboarding & Progress -- Payments & Billing -- Popups -- Pricing Sections -- Prompt Boxes -- Sidebars -- Standard Forms -- Standard Tables -- Stats -- Subscribe -- Tabs -- Timeline - -# New Library Component: Charts -**Date:** October 18, 2024 -**Version:** buridan/ui v0.1.0 - -- Area Charts -- Bar Charts -- Doughnut Charts -- Line Charts -- Pie Charts -- Radar Charts -- Scatter Charts - -# buridan/ui v0.0.1 Deployed to Reflex -**Date:** October 16, 2024 -**Version:** buridan/ui v0.0.1 - -# Initial Release -**Date:** October 5, 2024 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0f15cf1f..0ea73514 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,203 +1,35 @@ -# Buridan UI Contribution Guide +# Contributing to Buridan UI -Thank you for your interest in contributing to Buridan UI! This guide will help you understand how to add new components and documentation to the project. +We love contributions! Whether you're fixing a bug, adding a new component, or improving the theme engine, here's how you can help. -## Getting Started +## Development Setup -### Prerequisites +1. Fork and clone the repository. +2. Install dependencies using `uv` (recommended) or `pip install -r requirements.txt`. +3. Run the development server: `reflex run`. -- Python 3.10 or higher -- Git -- Basic knowledge of Reflex +## Adding Components -### Setting Up Development Environment +- New components should be added to `app/base_ui/components/base/`. +- Use `CoreComponent` or `BaseUIComponent` as base classes to ensure theme compatibility. +- Use `oklch` CSS variables for all colors. -1. Fork the repository and clone it to your local machine. -2. Install [uv](https://github.com/astral-sh/uv) if you don't have it already. It is a fast Python package installer. - ```bash - pip install uv - ``` -3. Create a virtual environment and install dependencies: - ```bash - uv venv - source .venv/bin/activate # On Windows: .venv\Scripts\activate - uv pip install -e . - ``` -4. Set up pre-commit hooks to automatically format your code. - ```bash - pre-commit install - ``` +## Adding Examples -## Project Structure +- Add your example card to `app/examples/components.py`. +- Use the `@masonry_card(label="Category")` decorator. +- Categories include: `General`, `Finance`, `Healthcare`, `Tech`, `Government`. -The project is organized as follows: +## Coding Standards -- `src/`: The main source directory for the application. - - `components/`: Core UI components and wrappers for the documentation site itself. - - `docs/`: Contains the logic for parsing and generating documentation. - - `library/`: **This is where component source code lives.** Components are organized into subdirectories by category (e.g., `components`, `charts`). - - `hooks.py`: Global client-side state management. - - `routes.py`: Dynamically generates documentation routes from markdown files. - - `templates/`: Contains the main layouts for the website (sidebar, navbar, etc.). - - `utils/`: Shared utility functions. - - `views/`: High-level page components, like the main landing page. - - `src.py`: The main Reflex application entry point. -- `docs/`: Contains the markdown files for the documentation pages. Each subdirectory corresponds to a section in the sidebar. +- Follow PEP 8 for Python code. +- Ensure all components are accessible (ARIA roles, keyboard support). +- Use `rx.cond` and `rx.match` for dynamic, client-side logic where possible. -## Adding New Components +## Pull Requests -The process for adding new components is streamlined to keep code and documentation in sync. +- Create a feature branch for your changes. +- Provide a clear description of the problem solved or the feature added. +- Include a screenshot or recording of new UI components. -### 1. Create the Component File - -1. Identify the correct category for your component under `src/docs/library/`. Common categories are `components`, `charts`, and `wrapped_components`. If a new category is needed, create a new subdirectory. -2. Create a new Python file for your component (e.g., `src/docs/library/components/my_component/my_component.py`) and a file for examples (`.../my_component/examples.py`). -3. Write your component as a Python function that returns a `reflex.Component`. It's best practice to create multiple functions in the `examples.py` file to showcase different variations of your component. - -**Example Component (`.../my_component/my_component.py`):** - -```python -import reflex as rx - -def my_component(*children, **props): - return rx.box( - "My new component!", - *children, - **props - ) -``` - -**Example for Docs (`.../my_component/examples.py`):** - -```python -import reflex as rx -from .my_component import my_component - -def my_component_demo(): - return my_component() - -def my_component_with_style(): - return my_component(style={"border": "1px solid red"}) -``` - -### 2. Create the Documentation Page - -1. Create a new markdown file in the corresponding `docs/` subdirectory. For example, `docs/components/my_component.md`. -2. Add frontmatter to the top of the file to configure its title and order in the sidebar. - - ```yaml - --- - title: My Component - order: 10 - --- - ``` - -### 3. Display the Component in Docs - -Use the custom markdown commands to render your component and its code. The parser automatically discovers any function in the `src/docs/library/` directory. - -- To show a live demo and its complete file source code: - `--demo_and_code_single_file(my_component_demo)--` - -- To show a live demo and only the source code of the specific function: - `--demo_and_single_function(my_component_with_style)--` - -- To show only the source code of a function: - `--show_code_with_language([my_component_demo, 'python'])--` - -- To render a component directly without code: - `--my_component_demo--` - -**Example Markdown (`docs/components/my_component.md`):** - - --- - title: My Component - order: 10 - --- - - # My Component - - This is a great new component. - - ## Basic Example - - --demo_and_single_function(my_component_demo)-- - - ## Styled Example - - Here is a component with a style. - - --demo_and_single_function(my_component_with_style)-- - - ## Full Source Code - - You can also view the full source code of the module. - - --full_source_page_of_component(my_component_demo)-- - -The routing and sidebar navigation will be updated automatically based on the new markdown file. - -## Testing Your Changes - -1. Run the development server: - ```bash - reflex run - ``` -2. Visit `http://localhost:3000` and navigate to your new documentation page to see your component in action. -3. Verify that your component renders correctly and works as expected. - -## Submitting Your Contribution - -1. Create a new branch for your feature: - ```bash - git checkout -b feature/new-component - ``` -2. Commit your changes with clear, descriptive commit messages. -3. Push your branch to your fork: - ```bash - git push origin feature/new-component - ``` -4. Create a pull request to the main repository. -5. In your PR description, include: - - A clear description of what you added. - - Screenshots if applicable. - - Any special considerations or notes for reviewers. - -## Best Practices - -### Component Design - -- Follow existing component patterns for consistency. -- Use responsive design principles. -- Comment your code, especially complex logic. -- Provide reasonable default values for all props. - -### Naming Conventions - -- Component files and functions should use `snake_case`. -- Directory names for component categories should be plural (e.g., `components`, `charts`). - -### Code Style - -- Follow PEP 8 guidelines. -- Use type hints where possible. -- Format your code with `black` and `ruff`. The pre-commit hooks should handle this automatically. - -## Troubleshooting - -### Common Issues - -- **Component not found in markdown**: Ensure your component function is located within the `src/docs/library/` directory and that you are using the correct function name in the markdown command. -- **Component not showing up**: Verify that the route is correctly generated by checking the `docs/` path and the frontmatter in your `.md` file. -- **Styling issues**: The project uses Tailwind CSS. Check `tailwind.config.js` and existing components for styling conventions. - -If you encounter issues not covered here, please open an issue on the repository. - -## Need Help? - -If you need assistance or have questions about contributing, please: -- Open an issue on the repository. -- Reach out to the maintainers. -- Check existing documentation and examples. - -Thank you for contributing to Buridan UI! \ No newline at end of file +Thank you for helping make Buridan UI the standard for Reflex applications! diff --git a/LICENSE.md b/LICENSE.md index 336766fc..0f6c1326 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,9 +1,21 @@ MIT License -Copyright (c) 2025 LineIndent +Copyright (c) 2026 Buridan UI -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 05e5841c..d21fdacd 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,6 @@ -> **Note (Feb 19, 2026)** -> This repo is unmaintained for the time being. I might come back to it at some point in the future. +# Buridan UI -# buridan/ui - -UI library for Reflex Developers. Extend, override, and ship. -

- Logo -

- - -## Docs - -Visit the [Docs](https://buridan-ui.reflex.run/docs/getting-started/introduction) section to get started. - - -## Contributing - -Please read this [guide](CONTRIBUTING.md) for more information on how to contribute. - -## License - -Licensed under the [MIT license](LICENSE.md). +Beautifully designed, data-oriented Reflex components for high-end internal tools. +Built for Python engineers working in Finance, Healthcare, Tech, and Government. +Features a deterministic theme engine with deterministic seed-based presets. +MIT Licensed and 100% Open Source. diff --git a/cli/__init__.py b/app/__init__.py similarity index 100% rename from cli/__init__.py rename to app/__init__.py diff --git a/app/app.py b/app/app.py new file mode 100644 index 00000000..11e44a84 --- /dev/null +++ b/app/app.py @@ -0,0 +1,108 @@ +import reflex as rx + +from app.pages.charts import chart_page +from app.pages.components import components_page +from app.pages.landing import landing_page +from app.templates.docpage import docpage +from app.templates.mainpage import mainpage +from app.templates.toc import table_of_content +from app.www.generator import generate_docs_library + +BURIDAN_URL = "https://buridan-create.reflex.run/" +BURIDAN_SLOGAN = ( + "Beautifully designed Reflex components to build your web apps faster. Open source." +) +BURIDAN_KEY_WORDS = ( + "buridan, ui, web apps, framework, open source, frontend, backend, full stack" +) +SITE_LOGO_URL = "https://raw.githubusercontent.com/buridan-ui/ui/refs/heads/main/assets/site/site_preview.webp" + +SITE_META_TAGS = [ + {"name": "application-name", "content": "Buridan UI"}, + {"name": "keywords", "content": BURIDAN_KEY_WORDS}, + {"name": "description", "content": BURIDAN_SLOGAN}, + {"property": "og:url", "content": BURIDAN_URL}, + {"property": "og:type", "content": "website"}, + {"property": "og:title", "content": "Buridan UI"}, + {"property": "og:description", "content": BURIDAN_SLOGAN}, + {"property": "og:image", "content": SITE_LOGO_URL}, + {"property": "og:image:width", "content": "1200"}, + {"property": "og:image:height", "content": "630"}, + {"name": "twitter:card", "content": "summary_large_image"}, + {"property": "twitter:domain", "content": BURIDAN_URL}, + {"property": "twitter:url", "content": BURIDAN_URL}, + {"name": "twitter:title", "content": "Buridan UI"}, + {"name": "twitter:description", "content": BURIDAN_SLOGAN}, + {"name": "twitter:image", "content": SITE_LOGO_URL}, +] + + +app = rx.App( + head_components=[ + rx.el.script(src="/prism/prism.js"), + ], + stylesheets=[ + "globals.css", + # --- Prism CSS --- + "prism/prism.css", + # --- Sans Fonts --- + "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap", + "https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap", + "https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap", + "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap", + "https://fonts.googleapis.com/css2?family=Public+Sans:wght@300;400;500;600;700&display=swap", + "https://cdn.jsdelivr.net/npm/geist@1.3.0/dist/font/sans.css", # Geist Sans + # --- Serif Fonts --- + "https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap", + "https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;1,300&display=swap", + "https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400..700;1,400..700&display=swap", + "https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap", + # --- Monospace Fonts --- + "https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&display=swap", + "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&display=swap", + "https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600&display=swap", + "https://cdn.jsdelivr.net/npm/geist@1.3.0/dist/font/mono.css", # Geist Mono + ], +) +app.add_page( + component=mainpage(), + route="/create", + title="New Project - buridan/ui", + meta=SITE_META_TAGS, +) + +app.add_page( + component=chart_page(), + route="/charts", + title="Charts - buridan/ui", + meta=SITE_META_TAGS, +) + +app.add_page( + component=components_page(), + route="/components", + title="Components - buridan/ui", + meta=SITE_META_TAGS, +) + +app.add_page( + component=landing_page(), + route="/", + title="The UI Library for Reflex Developers - buridan/ui", + meta=SITE_META_TAGS, +) + +# Add all the documentation pages +for doc in generate_docs_library(): + main_content = rx.el.div(*doc.component, class_name="w-full") + toc_content = table_of_content(doc.url, doc.table_of_content) + + title_s = doc.url.split("/")[-1].replace("-", " ").title() + title = f"{title_s} – buridan/ui" + + app.add_page( + docpage(main_content, toc_content), + route=f"/{doc.url}", + title=title, + # meta=meta.SITE_META_TAGS, + ) diff --git a/app/engine/actions.py b/app/engine/actions.py new file mode 100644 index 00000000..e0171809 --- /dev/null +++ b/app/engine/actions.py @@ -0,0 +1,703 @@ +import json + +from app.registry.colors import COLOR_THEMES +from app.registry.fonts import FONT_REGISTRY +from app.registry.radii import RADIUS_OPTIONS +from app.registry.styles import STYLE_REGISTRY +from app.registry.themes import BASE_THEMES + + +def _engine_js() -> str: + """ + Inline the full theme engine (registries + rebuildTheme + helpers). + Used in every action so nothing depends on window.__ globals surviving re-renders. + """ + style_registry_js = json.dumps(STYLE_REGISTRY) + base_themes_js = json.dumps(BASE_THEMES) + color_themes_js = json.dumps(COLOR_THEMES) + font_registry_js = json.dumps(FONT_REGISTRY) + radius_options_js = json.dumps(RADIUS_OPTIONS) + + return f""" + const _STYLE_REGISTRY = {style_registry_js}; + const _BASE_THEMES = {base_themes_js}; + const _COLOR_THEMES = {color_themes_js}; + const _FONT_REGISTRY = {font_registry_js}; + const _RADIUS_OPTIONS = {radius_options_js}; + const _CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + function _flattenVars(obj) {{ + const out = {{}}; + for (const [k, v] of Object.entries(obj)) {{ + out[k === 'radius' ? '--radius' : '--' + k] = v; + }} + return out; + }} + + function _toBase62(n, len) {{ + let res = ""; + for (let i = 0; i < len; i++) {{ + res += _CHARS[n % 62]; + n = Math.floor(n / 62); + }} + return res; + }} + + function _fromBase62(s) {{ + let n = 0; + for (let i = s.length - 1; i >= 0; i--) {{ + n = n * 62 + _CHARS.indexOf(s[i]); + }} + return n; + }} + + function _rebuildTheme(config) {{ + const {{ baseId, colorId, chartId, styleId, fontId, radius, darkMode }} = config; + const base = _BASE_THEMES.find(b => b.id === baseId); + if (!base) return {{}}; + + const baseVars = _flattenVars(darkMode ? base.dark : base.light); + let theme = {{ ...baseVars, '__base_id': baseId, '__base_label': base.label }}; + + if (colorId) {{ + const color = _COLOR_THEMES.find(c => c.id === colorId); + if (color) {{ + const cvars = _flattenVars(darkMode ? color.dark : color.light); + for (const [k, v] of Object.entries(cvars)) {{ + if (!k.startsWith('--chart-')) theme[k] = v; + }} + theme['__color_id'] = colorId; + theme['__color_label'] = color.label; + }} + }} else {{ + theme['__color_id'] = null; + theme['__color_label'] = null; + }} + + if (chartId) {{ + const chart = _COLOR_THEMES.find(c => c.id === chartId); + if (chart) {{ + const cvars = _flattenVars(darkMode ? chart.dark : chart.light); + for (const [k, v] of Object.entries(cvars)) {{ + if (k.startsWith('--chart-')) theme[k] = v; + }} + theme['__chart_id'] = chartId; + theme['__chart_label'] = chart.label; + }} + }} else {{ + theme['__chart_id'] = null; + theme['__chart_label'] = null; + }} + + if (styleId) {{ + const style = _STYLE_REGISTRY.find(s => s.id === styleId); + if (style) {{ + Object.assign(theme, style.vars); + theme['__style_id'] = styleId; + theme['__style_label'] = style.label; + }} + }} + + if (fontId) {{ + const font = _FONT_REGISTRY.find(f => f.id === fontId); + if (font) {{ + Object.assign(theme, font.vars); + theme['__font_id'] = fontId; + theme['__font_label'] = font.label; + }} + }} + + if (radius) {{ + theme['--radius'] = radius; + }} + + return theme; + }} + + function _encodeConfig(theme) {{ + const bIdx = _BASE_THEMES.findIndex(b => b.id === theme['__base_id']); + const cIdx = theme['__color_id'] ? _COLOR_THEMES.findIndex(c => c.id === theme['__color_id']) + 1 : 0; + const chIdx = theme['__chart_id'] ? _COLOR_THEMES.findIndex(c => c.id === theme['__chart_id']) + 1 : 0; + const sIdx = _STYLE_REGISTRY.findIndex(s => s.id === theme['__style_id']); + const fIdx = _FONT_REGISTRY.findIndex(f => f.id === theme['__font_id']); + const rIdx = _RADIUS_OPTIONS.findIndex(r => r[1] === theme['--radius']); + + if (bIdx === -1 || sIdx === -1 || fIdx === -1 || rIdx === -1) return null; + + // Default state special case + if (bIdx === 0 && cIdx === 0 && chIdx === 0 && sIdx === 0 && fIdx === 0 && rIdx === 2) return "b0"; + + // State space: 6 * 11 * 11 * 5 * 5 * 4 = 72600 + let n = (((((bIdx * 11 + cIdx) * 11 + chIdx) * 5 + sIdx) * 5 + fIdx) * 4 + rIdx); + + // Encode as 9 chars: [Base62(N, 4)] + [Base62(Checksum(N), 5)] + const checksum = (n * 12345) % 916132832; // 62^5 + return _toBase62(n, 4) + _toBase62(checksum, 5); + }} + + function _decodeSeed(seed) {{ + if (!seed) return null; + if (seed === "b0") {{ + return {{ baseId: _BASE_THEMES[0].id, colorId: null, chartId: null, styleId: _STYLE_REGISTRY[0].id, fontId: _FONT_REGISTRY[0].id, radius: _RADIUS_OPTIONS[2][1] }}; + }} + if (seed.length !== 9) return null; + + const n = _fromBase62(seed.substring(0, 4)); + const checksum = _fromBase62(seed.substring(4)); + + if (checksum === (n * 12345) % 916132832 && n < 72600) {{ + let temp = n; + const rIdx = temp % 4; temp = Math.floor(temp / 4); + const fIdx = temp % 5; temp = Math.floor(temp / 5); + const sIdx = temp % 5; temp = Math.floor(temp / 5); + const chIdx = temp % 11; temp = Math.floor(temp / 11); + const cIdx = temp % 11; temp = Math.floor(temp / 11); + const bIdx = temp % 6; + + return {{ + baseId: _BASE_THEMES[bIdx].id, + colorId: cIdx > 0 ? _COLOR_THEMES[cIdx - 1].id : null, + chartId: chIdx > 0 ? _COLOR_THEMES[chIdx - 1].id : null, + styleId: _STYLE_REGISTRY[sIdx].id, + fontId: _FONT_REGISTRY[fIdx].id, + radius: _RADIUS_OPTIONS[rIdx][1] + }}; + }} + return null; + }} + + function _hashStringToInt(str) {{ + let hash = 0; + for (let i = 0; i < str.length; i++) {{ + hash = (hash << 5) - hash + str.charCodeAt(i); + hash |= 0; + }} + return hash >>> 0; + }} + + function _mulberry32(seed) {{ + return function() {{ + let t = (seed += 0x6D2B79F5); + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }}; + }} + + function _generateFromSeed(seedString, darkMode) {{ + let config = _decodeSeed(seedString); + if (!config) {{ + // Legacy support for non-encoded random strings + const rand = _mulberry32(_hashStringToInt(seedString)); + config = {{ + baseId: _BASE_THEMES[Math.floor(rand() * _BASE_THEMES.length)].id, + colorId: (function(r){{ let i = Math.floor(r * 11); return i === 0 ? null : _COLOR_THEMES[i-1].id; }})(rand()), + chartId: (function(r){{ let i = Math.floor(r * 11); return i === 0 ? null : _COLOR_THEMES[i-1].id; }})(rand()), + styleId: _STYLE_REGISTRY[Math.floor(rand() * _STYLE_REGISTRY.length)].id, + fontId: _FONT_REGISTRY[Math.floor(rand() * _FONT_REGISTRY.length)].id, + radius: _RADIUS_OPTIONS[Math.floor(rand() * _RADIUS_OPTIONS.length)][1] + }}; + }} + + const theme = _rebuildTheme({{ ...config, darkMode }}); + return {{ ...theme, '__seed': seedString, '__dark': darkMode }}; + }} + + function _randomSeed() {{ + const n = Math.floor(Math.random() * 72600); + const checksum = (n * 12345) % 916132832; + return _toBase62(n, 4) + _toBase62(checksum, 5); + }} + """ + + +def _sync_sidebar_js() -> str: + """ + After applying a theme config, sync the sidebar ClientStateVars + so the dropdowns reflect the current state. + """ + return """ + if (window.__syncSidebar) window.__syncSidebar(config); + """ + + +SHUFFLE_JS = f""" +(function() {{ + {_engine_js()} + + const dark = refs['_client_state_darkmode'] || false; + const s = _randomSeed(); + + // update seed input if present + const el = document.getElementById('seed-input-el'); + if (el) el.value = s; + + const config = _generateFromSeed(s, dark); + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](s); + + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(s); +}})(); +""" + +APPLY_SEED_JS = f""" +(function() {{ + {_engine_js()} + + const dark = refs['_client_state_darkmode'] || false; + const el = document.getElementById('seed-input-el'); + const s = el ? el.value.trim() : ''; + if (!s) return; + + const config = _generateFromSeed(s, dark); + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](s); + + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(s); +}})(); +""" + +INITIAL_LOAD_JS = f""" +(function() {{ + {_engine_js()} + window.refs = refs; + + // 1. Helper: Robust URL Parsing + const getPresetFromURL = () => {{ + try {{ + const params = new URLSearchParams(window.location.search); + return params.get('preset'); + }} catch (e) {{ + return null; + }} + }}; + + // 2. Determine Dark Mode + const savedTheme = localStorage.getItem('theme'); + let isDark = false; + if (savedTheme === 'dark') {{ + isDark = true; + }} else if (savedTheme === 'light') {{ + isDark = false; + }} else {{ + isDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + }} + + if (isDark) {{ + document.documentElement.classList.add('dark'); + }} else {{ + document.documentElement.classList.remove('dark'); + }} + + if (refs['_client_state_setDarkmode']) refs['_client_state_setDarkmode'](isDark); + + // 2. Check for Welcome Dialog + const hasSeenWelcome = localStorage.getItem('has_seen_welcome'); + if (!hasSeenWelcome) {{ + if (refs['_client_state_setWelcome_open']) refs['_client_state_setWelcome_open'](true); + }} + + // 3. Determine Seed (Prioritize URL > current state > default) + const urlSeed = getPresetFromURL(); + const currentSeed = refs['_client_state_seed']; + const s = urlSeed || currentSeed || "b0"; + + // 4. Generate and Apply Theme + const config = _generateFromSeed(s, isDark); + + const applyConfig = () => {{ + if (refs['_client_state_setTheme']) refs['_client_state_setTheme'](config); + if (refs['_client_state_setSeed']) refs['_client_state_setSeed'](s); + + // Sync sidebar if the helper exists, otherwise retry soon + if (window.__syncSidebar) {{ + window.__syncSidebar(config); + }} + + // Ensure URL stays in sync + if (window.__updatePresetURL) {{ + window.__updatePresetURL(s, false); + }} + + const el = document.getElementById('seed-input-el'); + if (el) el.value = s; + }}; + + // Execute sequence + applyConfig(); + + // Retries to catch late-binding sidebar/url scripts + setTimeout(applyConfig, 50); + setTimeout(applyConfig, 200); +}})(); +""" + +TOGGLE_DARK_JS = f""" +(function() {{ + {_engine_js()} + + const current = refs['_client_state_theme'] || {{}}; + const nowDark = !(current['__dark'] || false); + refs['_client_state_setDarkmode'](nowDark); + + // Toggle root class for Tailwind + if (nowDark) {{ + document.documentElement.classList.add('dark'); + localStorage.setItem('theme', 'dark'); + }} else {{ + document.documentElement.classList.remove('dark'); + localStorage.setItem('theme', 'light'); + }} + + let config; + const s = current['__seed'] || refs['_client_state_seed'] || ''; + if (s) {{ + config = _generateFromSeed(s, nowDark); + }} else {{ + config = _rebuildTheme({{ + baseId: current['__base_id'] || 'neutral', + colorId: current['__color_id'] || null, + chartId: current['__chart_id'] || null, + styleId: current['__style_id'] || null, + fontId: current['__font_id'] || null, + radius: current['--radius'] || null, + darkMode: nowDark, + }}); + config = {{ ...current, ...config, '__dark': nowDark }}; + }} + + refs['_client_state_setTheme'](config); + {_sync_sidebar_js()} +}})(); +""" + + +RESET_JS = f""" +(function() {{ + {_engine_js()} + + const dark = refs['_client_state_darkmode'] || false; + const s = "b0"; + + const config = _generateFromSeed(s, dark); + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](s); + + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(s); +}})(); +""" + + +def _apply_base_theme_js(base_id: str) -> str: + return f""" + (function() {{ + {_engine_js()} + const current = refs['_client_state_theme'] || {{}}; + const dark = refs['_client_state_darkmode'] || false; + let config = _rebuildTheme({{ + baseId: '{base_id}', + colorId: current['__color_id'] || null, + chartId: current['__chart_id'] || null, + styleId: current['__style_id'] || null, + fontId: current['__font_id'] || null, + radius: current['--radius'] || null, + darkMode: dark, + }}); + const newSeed = _encodeConfig(config); + config['__seed'] = newSeed; + config['__dark'] = dark; + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](newSeed); + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(newSeed); + }})(); + """ + + +def _apply_color_theme_js(color_id: str) -> str: + return f""" + (function() {{ + {_engine_js()} + const current = refs['_client_state_theme'] || {{}}; + const dark = refs['_client_state_darkmode'] || false; + let config = _rebuildTheme({{ + baseId: current['__base_id'] || 'neutral', + colorId: '{color_id}', + chartId: current['__chart_id'] || null, + styleId: current['__style_id'] || null, + fontId: current['__font_id'] || null, + radius: current['--radius'] || null, + darkMode: dark, + }}); + const newSeed = _encodeConfig(config); + config['__seed'] = newSeed; + config['__dark'] = dark; + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](newSeed); + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(newSeed); + }})(); + """ + + +def _apply_chart_color_js(color_id: str) -> str: + return f""" + (function() {{ + {_engine_js()} + const current = refs['_client_state_theme'] || {{}}; + const dark = refs['_client_state_darkmode'] || false; + let config = _rebuildTheme({{ + baseId: current['__base_id'] || 'neutral', + colorId: current['__color_id'] || null, + chartId: '{color_id}', + styleId: current['__style_id'] || null, + fontId: current['__font_id'] || null, + radius: current['--radius'] || null, + darkMode: dark, + }}); + const newSeed = _encodeConfig(config); + config['__seed'] = newSeed; + config['__dark'] = dark; + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](newSeed); + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(newSeed); + }})(); + """ + + +def _apply_style_js(style_id: str) -> str: + return f""" + (function() {{ + {_engine_js()} + const current = refs['_client_state_theme'] || {{}}; + const dark = refs['_client_state_darkmode'] || false; + let config = _rebuildTheme({{ + baseId: current['__base_id'] || 'neutral', + colorId: current['__color_id'] || null, + chartId: current['__chart_id'] || null, + styleId: '{style_id}', + fontId: current['__font_id'] || null, + darkMode: dark, + }}); + const newSeed = _encodeConfig(config); + config['__seed'] = newSeed; + config['__dark'] = dark; + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](newSeed); + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(newSeed); + }})(); + """ + + +def _apply_font_js(font_id: str) -> str: + return f""" + (function() {{ + {_engine_js()} + const current = refs['_client_state_theme'] || {{}}; + const dark = refs['_client_state_darkmode'] || false; + let config = _rebuildTheme({{ + baseId: current['__base_id'] || 'neutral', + colorId: current['__color_id'] || null, + chartId: current['__chart_id'] || null, + styleId: current['__style_id'] || null, + fontId: '{font_id}', + radius: current['--radius'] || null, + darkMode: dark, + }}); + const newSeed = _encodeConfig(config); + config['__seed'] = newSeed; + config['__dark'] = dark; + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](newSeed); + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(newSeed); + }})(); + """ + + +APPLY_BASE_PRIMARY_JS = f""" +(function() {{ + {_engine_js()} + const current = refs['_client_state_theme'] || {{}}; + const dark = refs['_client_state_darkmode'] || false; + let config = _rebuildTheme({{ + baseId: current['__base_id'] || 'neutral', + colorId: null, + chartId: current['__chart_id'] || null, + styleId: current['__style_id'] || null, + fontId: current['__font_id'] || null, + darkMode: dark, + }}); + const newSeed = _encodeConfig(config); + config['__seed'] = newSeed; + config['__dark'] = dark; + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](newSeed); + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(newSeed); +}})(); +""" + +APPLY_BASE_CHARTS_JS = f""" +(function() {{ + {_engine_js()} + const current = refs['_client_state_theme'] || {{}}; + const dark = refs['_client_state_darkmode'] || false; + let config = _rebuildTheme({{ + baseId: current['__base_id'] || 'neutral', + colorId: current['__color_id'] || null, + chartId: null, + styleId: current['__style_id'] || null, + fontId: current['__font_id'] || null, + darkMode: dark, + }}); + const newSeed = _encodeConfig(config); + config['__seed'] = newSeed; + config['__dark'] = dark; + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](newSeed); + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(newSeed); +}})(); +""" + +ADD_SWATCHES_JS = """ +(function() { + function injectSwatches() { + const block = document.getElementById("css-export-block"); + if (!block) return; + + // Ensure we are targeting the inner block text wrapper + const codeElement = block.querySelector("code") || block; + + // Prevent infinite loops if already formatted + if (codeElement.dataset.swatchesDone === "true") return; + + let rawText = codeElement.textContent; + + // Match all variations of oklch(...) globally + const oklchRegex = /oklch\([^)]+\)/g; + + // Map every text match to an HTML template containing the swatch + the string itself + const highLightedHtml = rawText.replace(oklchRegex, (match) => { + const swatchHtml = ``; + return swatchHtml + match; + }); + + codeElement.innerHTML = highLightedHtml; + codeElement.dataset.swatchesDone = "true"; + } + + // Run quickly across standard rendering cycles + setTimeout(injectSwatches, 40); + setTimeout(injectSwatches, 120); + setTimeout(injectSwatches, 300); +})(); +""" + + +FORMAT_CSS_JS = f""" +(function() {{ + {_engine_js()} + + const theme = refs['_client_state_theme'] || {{}}; + const seed = theme['__seed'] || 'b0'; + + const lightConfig = _generateFromSeed(seed, false); + const darkConfig = _generateFromSeed(seed, true); + + const format = (config) => + Object.entries(config) + .filter(([k]) => k.startsWith('--')) + .map(([k, v]) => ` ${{k}}: ${{v}};`) + .join('\\n'); + + const css = `:root {{\\n${{format(lightConfig)}}\\n}}\\n\\n.dark {{\\n${{format(darkConfig)}}\\n}}`; + + // 1. Send the clean raw string text back to state + refs['_client_state_setCssoutput'](css); + + // 2. Wait for React render state update, then cleanly map HTML templates + setTimeout(() => {{ + const block = document.getElementById("css-export-block"); + if (!block) return; + + const codeElement = block.querySelector("code") || block; + let rawText = codeElement.textContent; + + const oklchRegex = /oklch\\([^)]+\\)/g; + const highLightedHtml = rawText.replace(oklchRegex, (match) => {{ + return `` + match; + }}); // <-- FIXED: Doubled the function body closing brace '}}' + + codeElement.innerHTML = highLightedHtml; + codeElement.dataset.swatchesDone = "true"; + }}, 60); +}})(); +""" + + +def _patch_js(updates: dict) -> str: + updates_js = json.dumps(updates) + return f""" + (function() {{ + {_engine_js()} + const current = refs['_client_state_theme'] || {{}}; + const dark = refs['_client_state_darkmode'] || false; + let config = {{...current, ...{updates_js}}}; + + // Ensure rebuild to get IDs for encoding + config = _rebuildTheme({{ + baseId: config['__base_id'] || 'neutral', + colorId: config['__color_id'] || null, + chartId: config['__chart_id'] || null, + styleId: config['__style_id'] || null, + fontId: config['__font_id'] || null, + radius: config['--radius'] || null, + darkMode: dark + }}); + + const newSeed = _encodeConfig(config); + config['__seed'] = newSeed; + config['__dark'] = dark; + + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](newSeed); + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(newSeed); + }})(); + """ + + +def _patch_radius_js(value: str) -> str: + return f""" + (function() {{ + {_engine_js()} + const current = refs['_client_state_theme'] || {{}}; + const dark = refs['_client_state_darkmode'] || false; + + let config = _rebuildTheme({{ + baseId: current['__base_id'] || 'neutral', + colorId: current['__color_id'] || null, + chartId: current['__chart_id'] || null, + styleId: current['__style_id'] || 'vega', + fontId: current['__font_id'] || 'inter', + radius: '{value}', + darkMode: dark, + }}); + + const newSeed = _encodeConfig(config); + config['__seed'] = newSeed; + config['__dark'] = dark; + + refs['_client_state_setTheme'](config); + refs['_client_state_setSeed'](newSeed); + {_sync_sidebar_js()} + if (window.__updatePresetURL) window.__updatePresetURL(newSeed); + }})(); + """ diff --git a/app/engine/seed.py b/app/engine/seed.py new file mode 100644 index 00000000..39d350c1 --- /dev/null +++ b/app/engine/seed.py @@ -0,0 +1,230 @@ +import json + +import reflex as rx + +from app.registry.colors import COLOR_THEMES +from app.registry.fonts import FONT_REGISTRY +from app.registry.radii import RADIUS_OPTIONS +from app.registry.styles import STYLE_REGISTRY +from app.registry.themes import BASE_THEMES + + +def seed_engine() -> rx.Script: + style_registry_js = json.dumps(STYLE_REGISTRY) + base_themes_js = json.dumps(BASE_THEMES) + color_themes_js = json.dumps(COLOR_THEMES) + font_registry_js = json.dumps(FONT_REGISTRY) + radius_options_js = json.dumps(RADIUS_OPTIONS) + + return rx.script(f""" + function hashStringToInt(str) {{ + let hash = 0; + for (let i = 0; i < str.length; i++) {{ + hash = (hash << 5) - hash + str.charCodeAt(i); + hash |= 0; + }} + return hash >>> 0; + }} + + function mulberry32(seed) {{ + return function() {{ + let t = (seed += 0x6D2B79F5); + t = Math.imul(t ^ (t >>> 15), t | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }}; + }} + + const STYLE_REGISTRY = {style_registry_js}; + const BASE_THEMES = {base_themes_js}; + const COLOR_THEMES = {color_themes_js}; + const FONT_REGISTRY = {font_registry_js}; + const RADIUS_OPTIONS = {radius_options_js}; + const CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + + // Flatten a shadcn cssVars object → prefixed CSS var dict + // e.g. {{ primary: "oklch(...)" }} → {{ "--primary": "oklch(...)" }} + function flattenVars(obj) {{ + const out = {{}}; + for (const [k, v] of Object.entries(obj)) {{ + out[k === 'radius' ? '--radius' : '--' + k] = v; + }} + return out; + }} + + function toBase62(n, len) {{ + let res = ""; + for (let i = 0; i < len; i++) {{ + res += CHARS[n % 62]; + n = Math.floor(n / 62); + }} + return res; + }} + + function fromBase62(s) {{ + let n = 0; + for (let i = s.length - 1; i >= 0; i--) {{ + n = n * 62 + CHARS.indexOf(s[i]); + }} + return n; + }} + + function encodeConfig(theme) {{ + const bIdx = BASE_THEMES.findIndex(b => b.id === theme['__base_id']); + const cIdx = theme['__color_id'] ? COLOR_THEMES.findIndex(c => c.id === theme['__color_id']) + 1 : 0; + const chIdx = theme['__chart_id'] ? COLOR_THEMES.findIndex(c => c.id === theme['__chart_id']) + 1 : 0; + const sIdx = STYLE_REGISTRY.findIndex(s => s.id === theme['__style_id']); + const fIdx = FONT_REGISTRY.findIndex(f => f.id === theme['__font_id']); + const rIdx = RADIUS_OPTIONS.findIndex(r => r[1] === theme['--radius']); + + if (bIdx === -1 || sIdx === -1 || fIdx === -1 || rIdx === -1) return null; + + // Default state special case + if (bIdx === 0 && cIdx === 0 && chIdx === 0 && sIdx === 0 && fIdx === 0 && rIdx === 2) return "b0"; + + // State space: 6 * 11 * 11 * 5 * 5 * 4 = 72600 + let n = (((((bIdx * 11 + cIdx) * 11 + chIdx) * 5 + sIdx) * 5 + fIdx) * 4 + rIdx); + + // Encode as 9 chars: [Base62(N, 4)] + [Base62(Checksum(N), 5)] + const checksum = (n * 12345) % 916132832; // 62^5 + return toBase62(n, 4) + toBase62(checksum, 5); + }} + + function decodeSeed(seed) {{ + if (!seed) return null; + if (seed === "b0") {{ + return {{ baseId: BASE_THEMES[0].id, colorId: null, chartId: null, styleId: STYLE_REGISTRY[0].id, fontId: FONT_REGISTRY[0].id, radius: RADIUS_OPTIONS[2][1] }}; + }} + if (seed.length !== 9) return null; + + const n = fromBase62(seed.substring(0, 4)); + const checksum = fromBase62(seed.substring(4)); + + if (checksum === (n * 12345) % 916132832 && n < 72600) {{ + let temp = n; + const rIdx = temp % 4; temp = Math.floor(temp / 4); + const fIdx = temp % 5; temp = Math.floor(temp / 5); + const sIdx = temp % 5; temp = Math.floor(temp / 5); + const chIdx = temp % 11; temp = Math.floor(temp / 11); + const cIdx = temp % 11; temp = Math.floor(temp / 11); + const bIdx = temp % 6; + + return {{ + baseId: BASE_THEMES[bIdx].id, + colorId: cIdx > 0 ? COLOR_THEMES[cIdx - 1].id : null, + chartId: chIdx > 0 ? COLOR_THEMES[chIdx - 1].id : null, + styleId: STYLE_REGISTRY[sIdx].id, + fontId: FONT_REGISTRY[fIdx].id, + radius: RADIUS_OPTIONS[rIdx][1] + }}; + }} + return null; + }} + + function generateFromSeed(seedString, darkMode) {{ + let config = decodeSeed(seedString); + if (!config) {{ + const rand = mulberry32(hashStringToInt(seedString)); + config = {{ + baseId: BASE_THEMES[Math.floor(rand() * BASE_THEMES.length)].id, + colorId: (function(r){{ let i = Math.floor(r * 11); return i === 0 ? null : COLOR_THEMES[i-1].id; }})(rand()), + chartId: (function(r){{ let i = Math.floor(r * 11); return i === 0 ? null : COLOR_THEMES[i-1].id; }})(rand()), + styleId: STYLE_REGISTRY[Math.floor(rand() * STYLE_REGISTRY.length)].id, + fontId: FONT_REGISTRY[Math.floor(rand() * FONT_REGISTRY.length)].id, + radius: RADIUS_OPTIONS[Math.floor(rand() * RADIUS_OPTIONS.length)][1] + }}; + }} + + const theme = rebuildTheme({{ + ...config, + darkMode: darkMode + }}); + + return {{ + ...theme, + "__seed": seedString, + "__dark": darkMode, + }}; + }} + + function rebuildTheme(config) {{ + const {{ baseId, colorId, chartId, styleId, fontId, radius, darkMode }} = config; + const base = BASE_THEMES.find(b => b.id === baseId); + if (!base) return {{}}; + + const baseVars = flattenVars(darkMode ? base.dark : base.light); + let theme = {{ ...baseVars, "__base_id": baseId, "__base_label": base.label }}; + + if (colorId) {{ + const color = COLOR_THEMES.find(c => c.id === colorId); + if (color) {{ + const cvars = flattenVars(darkMode ? color.dark : color.light); + for (const [k, v] of Object.entries(cvars)) {{ + if (!k.startsWith('--chart-')) theme[k] = v; + }} + theme["__color_id"] = colorId; + theme["__color_label"] = color.label; + }} + }} else {{ + theme["__color_id"] = null; + theme["__color_label"] = null; + }} + + if (chartId) {{ + const chart = COLOR_THEMES.find(c => c.id === chartId); + if (chart) {{ + const cvars = flattenVars(darkMode ? chart.dark : chart.light); + for (const [k, v] of Object.entries(cvars)) {{ + if (k.startsWith('--chart-')) theme[k] = v; + }} + theme["__chart_id"] = chartId; + theme["__chart_label"] = chart.label; + }} + }} else {{ + theme["__chart_id"] = null; + theme["__chart_label"] = null; + }} + + if (styleId) {{ + const style = STYLE_REGISTRY.find(s => s.id === styleId); + if (style) {{ + Object.assign(theme, style.vars); + theme["__style_id"] = styleId; + theme["__style_label"] = style.label; + }} + }} + + if (fontId) {{ + const font = FONT_REGISTRY.find(f => f.id === fontId); + if (font) {{ + Object.assign(theme, font.vars); + theme["__font_id"] = fontId; + theme["__font_label"] = font.label; + }} + }} + + if (radius) {{ + theme["--radius"] = radius; + }} + + return theme; + }} + + function randomSeed() {{ + const n = Math.floor(Math.random() * 72600); + const checksum = (n * 12345) % 916132832; + return toBase62(n, 4) + toBase62(checksum, 5); + }} + + window.__STYLE_REGISTRY = STYLE_REGISTRY; + window.__BASE_THEMES = BASE_THEMES; + window.__COLOR_THEMES = COLOR_THEMES; + window.__FONT_REGISTRY = FONT_REGISTRY; + window.__RADIUS_OPTIONS = RADIUS_OPTIONS; + window.__generateFromSeed = generateFromSeed; + window.__rebuildTheme = rebuildTheme; + window.__randomSeed = randomSeed; + window.__flattenVars = flattenVars; + window.__encodeConfig = encodeConfig; + window.__decodeSeed = decodeSeed; + """) diff --git a/app/engine/url_sync.py b/app/engine/url_sync.py new file mode 100644 index 00000000..5e036cf8 --- /dev/null +++ b/app/engine/url_sync.py @@ -0,0 +1,91 @@ +import reflex as rx + +def url_sync_engine() -> rx.Component: + return rx.script(""" + window.__getPresetFromURL = function() { + const params = new URLSearchParams(window.location.search); + return params.get('preset'); + }; + + window.__updatePresetURL = function(seed, push = true) { + if (!seed) return; + const url = new URL(window.location); + if (url.searchParams.get('preset') === seed) return; + + url.searchParams.set('preset', seed); + try { + if (push) { + window.history.pushState({ preset: seed }, '', url); + } else { + window.history.replaceState({ preset: seed }, '', url); + } + } catch (e) { + console.error("Failed to update URL:", e); + } + }; + + window.__syncSidebar = function(config) { + if (!config || !window.refs) return; + + const refs = window.refs; + const _BASE_THEMES = window.__BASE_THEMES; + const _COLOR_THEMES = window.__COLOR_THEMES; + const _RADIUS_OPTIONS = window.__RADIUS_OPTIONS; + + if (!_BASE_THEMES || !_COLOR_THEMES || !_RADIUS_OPTIONS) return; + + const _base = _BASE_THEMES.find(b => b.id === config['__base_id']); + if (_base) { + const _ring = (_base.light || {})['ring'] || ''; + if (refs['_client_state_setBase_theme_color']) refs['_client_state_setBase_theme_color'](_ring); + if (refs['_client_state_setSelected_base_color_cs']) refs['_client_state_setSelected_base_color_cs'](_base.label); + } + + const _colorId = config['__color_id']; + if (_colorId) { + const _col = _COLOR_THEMES.find(c => c.id === _colorId); + if (refs['_client_state_setTheme_color']) refs['_client_state_setTheme_color'](_col ? (_col.light || {})['primary'] || '' : ''); + if (_col && refs['_client_state_setSelected_theme_cs']) refs['_client_state_setSelected_theme_cs'](_col.label); + } else { + if (refs['_client_state_setTheme_color']) refs['_client_state_setTheme_color'](''); + if (refs['_client_state_setSelected_theme_cs']) refs['_client_state_setSelected_theme_cs'](refs['_client_state_selected_base_color_cs']); + } + + const _chartId = config['__chart_id']; + if (_chartId) { + const _ch = _COLOR_THEMES.find(c => c.id === _chartId); + if (refs['_client_state_setChart_color']) refs['_client_state_setChart_color'](_ch ? (_ch.light || {})['primary'] || '' : ''); + if (_ch && refs['_client_state_setSelected_chart_cs']) refs['_client_state_setSelected_chart_cs'](_ch.label); + } else { + if (refs['_client_state_setChart_color']) refs['_client_state_setChart_color'](''); + if (refs['_client_state_setSelected_chart_cs']) refs['_client_state_setSelected_chart_cs'](refs['_client_state_selected_base_color_cs']); + } + + if (config['__style_label'] && refs['_client_state_setSelected_style_cs']) refs['_client_state_setSelected_style_cs'](config['__style_label']); + if (config['__font_label'] && refs['_client_state_setSelected_font_cs']) refs['_client_state_setSelected_font_cs'](config['__font_label']); + + const _radius = config['--radius']; + if (_radius) { + const _radOption = _RADIUS_OPTIONS.find(r => r[1] === _radius); + if (_radOption && refs['_client_state_setSelected_radius_cs']) refs['_client_state_setSelected_radius_cs'](_radOption[0]); + } + }; + + window.addEventListener('popstate', (event) => { + const seed = (event.state && event.state.preset) || window.__getPresetFromURL(); + if (seed && window.refs) { + const dark = window.refs['_client_state_darkmode'] || false; + if (window.__generateFromSeed) { + const config = window.__generateFromSeed(seed, dark); + window.refs['_client_state_setTheme'](config); + window.refs['_client_state_setSeed'](seed); + window.__syncSidebar(config); + } else if (window._generateFromSeed) { // Check both global and engine scopes + const config = window._generateFromSeed(seed, dark); + window.refs['_client_state_setTheme'](config); + window.refs['_client_state_setSeed'](seed); + window.__syncSidebar(config); + } + } + }); + """) diff --git a/app/examples/components.py b/app/examples/components.py new file mode 100644 index 00000000..3fd219f8 --- /dev/null +++ b/app/examples/components.py @@ -0,0 +1,2297 @@ +import reflex as rx + +from app.examples.utils import masonry_card +from app.hooks import selected_font_cs +from components.icons.hugeicon import hi +from components.ui.accordion import accordion +from components.ui.avatar import avatar +from components.ui.badge import badge +from components.ui.button import button +from components.ui.checkbox import checkbox +from components.ui.input import input +from components.ui.input_group import ( + input_with_addons, + textarea_with_footer, +) +from components.ui.metric import metric +from components.ui.slider import slider +from components.ui.switch import switch +from components.ui.table import table +from components.ui.tabs import tabs +from components.utils.twmerge import cn + + +@masonry_card(label="General") +def card_one() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.p( + "Standard Actions", class_name="text-lg font-semibold text-foreground" + ), + rx.el.p( + "Basic component variants", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + rx.el.div( + rx.el.div( + button("Default", variant="default", size="sm"), + button("Secondary", variant="secondary", size="sm"), + button("Outline", variant="outline", size="sm"), + class_name="w-full grid grid-cols-3 items-center gap-x-2", + ), + rx.el.div( + input(placeholder="name", class_name="!bg-secondary"), + textarea_with_footer( + placeholder="Enter your message", + footer_text="120 characters left", + class_name="!bg-secondary", + ), + class_name="flex flex-col gap-y-2", + ), + class_name="flex flex-col gap-y-4", + ), + rx.el.div( + button("Close", variant="default", size="sm"), + button("Send Text", variant="outline", size="sm"), + class_name="w-full grid grid-cols-2 items-center gap-x-2", + ), + class_name="w-full flex flex-col gap-y-card", + ) + + +@masonry_card(label="General") +def card_two() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.p("Project Team", class_name="text-lg font-semibold text-foreground"), + rx.el.p( + "Manage collaborators", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + rx.el.div( + rx.el.div( + avatar( + src="https://avatars.githubusercontent.com/u/84860195?v=4", + alt="@buridan-ui", + fallback="BUI", + class_name="size-12", + ), + avatar( + src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", + alt="@reflex", + fallback="RE", + class_name="size-12", + ), + class_name=( + "flex -space-x-2 " + "*:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-[var(--background)] " + "*:data-[slot=avatar]:grayscale" + ), + ), + rx.el.p( + "No Team Members", class_name="text-md font-medium text-foreground" + ), + rx.el.p( + "Invite your team to collaborate on this project.", + class_name="text-sm font-light text-muted-foreground text-center w-full max-w-[200px]", + ), + class_name="flex flex-col items-center gap-y-2", + ), + rx.el.div( + button("Invite Members", variant="default", class_name="w-full"), + class_name="w-full", + ), + class_name="w-full flex flex-col gap-y-card items-center justify-center text-center", + ) + + +@masonry_card(label="General") +def card_three() -> rx.Component: + + room_controls = [ + {"icon": "Sun02Icon", "title": "Brightness", "value": 30}, + {"icon": "TemperatureIcon", "title": "Color temp", "value": 20}, + {"icon": "VolumeHighIcon", "title": "Volume", "value": 50}, + {"icon": "Timer01Icon", "title": "Fade", "value": 10}, + ] + + return rx.el.div( + rx.el.div( + rx.el.p( + "Central Room Command", class_name="text-lg font-medium text-foreground" + ), + rx.el.p( + "Hue Color Ambient", + class_name="text-sm font-light text-muted-foreground text-center w-full", + ), + class_name="flex flex-col items-center gap-y-1", + ), + rx.el.div( + rx.el.div( + button("Cooking", variant="outline", size="sm"), + button("Dining", variant="outline", size="sm"), + button("Light", variant="outline", size="sm"), + button("Focus", variant="outline", size="sm"), + class_name="w-full grid grid-cols-4 items-center gap-x-2", + ), + rx.el.div( + *[ + rx.el.div( + # Left Side: Icon + Title + rx.el.div( + hi(item["icon"], class_name="size-4"), + rx.el.p(item["title"], class_name="text-sm font-medium"), + class_name="flex flex-row items-center justify-start gap-x-4", + ), + # Right Side: The Slider Control + rx.el.div( + slider.root( + slider.control( + slider.track( + slider.indicator(class_name="!h-2"), + slider.thumb(class_name="size-3"), + class_name="!h-2", + ), + ), + default_value=item["value"], + ), + ), + class_name="w-full grid grid-cols-2 items-center gap-x-2 border border-input rounded-radius p-2.5", + ) + for item in room_controls + ], + class_name="w-full flex flex-col gap-y-2", + ), + class_name="flex flex-col gap-y-4 w-full", + ), + class_name="w-full flex flex-col gap-y-card items-center justify-center text-foreground", + ) + + +def _accordion_icon() -> rx.Component: + return hi( + "PlusSignIcon", + class_name="size-3 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45", + data_slot="accordion-trigger-icon", + ) + + +def _accordion_item(item: dict) -> rx.Component: + """Build a single accordion item from a dict with trigger/content/value keys.""" + return accordion.item( + accordion.header( + accordion.trigger( + render_=rx.el.button( + rx.el.span( + item["trigger"], + class_name=( + "text-sm font-medium text-left whitespace-normal " + "break-words leading-snug" + ), + ), + _accordion_icon(), + class_name=( + "w-full flex items-start justify-between gap-4 " + "group py-3 bg-transparent hover:bg-transparent " + "cursor-pointer" + ), + ), + ), + ), + accordion.panel( + rx.el.div( + item["content"], + data_slot="accordion-panel-div", + class_name="text-sm pb-2 text-muted-foreground", + ), + ), + value=item.get("value", ""), + ) + + +def accordion_general() -> rx.Component: + faq_items = [ + { + "trigger": "How do I update my account email address?", + "content": rx.el.p( + "Navigate to Profile Settings, click 'Edit' next to your email, " + "enter your new address, and confirm it via the verification link " + "sent to your inbox." + ), + "value": "general-1", + }, + { + "trigger": "Can I enable two-factor authentication (2FA)?", + "content": rx.el.p( + "Yes. Go to Security Settings, click 'Enable 2FA', and scan the QR " + "code using an authenticator app like Google Authenticator or 1Password." + ), + "value": "general-2", + }, + { + "trigger": "How do I change my workspace theme preferences?", + "content": rx.el.p( + "Under Preferences, you can toggle between Light, Dark, or System mode." + ), + "value": "general-3", + }, + ] + + return accordion.root( + *[_accordion_item(item) for item in faq_items], + default_value=["general-1"], + multiple=False, + class_name="w-full mx-auto p-2", + ) + + +def accordion_billing() -> rx.Component: + billing_items = [ + { + "trigger": "What is the difference between Basic and Pro tier pricing?", + "content": rx.el.div( + rx.el.p( + "Basic includes budgeting, goal tracking, and up to 3 linked accounts. " + "Pro adds unlimited accounts and support" + ), + class_name="py-2 text-sm text-muted-foreground", + ), + "value": "billing-1", + }, + { + "trigger": "When will my payment method be automatically charged?", + "content": rx.el.div( + rx.el.p( + "Subscriptions renew automatically every 30 days starting from your " + "initial upgrade date." + ), + class_name="py-2 text-sm text-muted-foreground", + ), + "value": "billing-2", + }, + { + "trigger": "Where can I find and download my historical invoices?", + "content": rx.el.div( + rx.el.p( + "Go to your Billing Dashboard and scroll down to the 'Invoices' " + "history section." + ), + class_name="py-2 text-sm text-muted-foreground", + ), + "value": "billing-3", + }, + ] + + return accordion.root( + *[_accordion_item(item) for item in billing_items], + default_value=["billing-1"], + multiple=False, + class_name="w-full mx-auto p-2", + ) + + +@masonry_card(label="General") +def card_four() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.p( + "Help & Support", class_name="text-lg font-semibold text-foreground" + ), + rx.el.p( + "Frequently asked questions", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + tabs.root( + tabs.list( + tabs.indicator(), + tabs.tab("General", value="general"), + tabs.tab("Billings", value="billings"), + class_name="w-full border-b border-input/60 pb-1", + ), + rx.el.div( + tabs.panel(accordion_general(), value="general", class_name="w-full"), + tabs.panel(accordion_billing(), value="billings", class_name="w-full"), + class_name="w-full", + ), + default_value="general", + class_name="w-full flex flex-col", + ), + rx.el.div( + button("Contact Support", variant="secondary", class_name="w-full"), + class_name="w-full", + ), + class_name="w-full flex flex-col gap-y-card items-stretch justify-start text-foreground overflow-hidden", + ) + + +@masonry_card(label="Finance") +def card_five() -> rx.Component: + summary_rows = [ + {"label": "Estimated arrival", "value": "Today, Apr 14", "bold": False}, + {"label": "Transaction fee", "value": "$0.00", "bold": False}, + {"label": "Total amount", "value": "$1,200.00", "bold": True}, + ] + + return rx.el.div( + # Header + rx.el.div( + rx.el.div( + rx.el.p( + "Transfer Funds", class_name="text-lg font-semibold text-foreground" + ), + rx.el.p( + "Move money between your connected accounts.", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + button( + hi("Cancel01Icon", class_name="size-4"), + variant="ghost", + size="sm", + class_name="!px-2 !py-2 self-start", + ), + class_name="w-full flex flex-row items-start justify-between", + ), + # Form Body + rx.el.div( + # Amount to Transfer + rx.el.div( + rx.el.p( + "Amount to Transfer", + class_name="text-sm font-semibold text-foreground", + ), + input_with_addons( + placeholder="1,200.00", + class_name="!bg-secondary px-1", + prefix="$", + suffix="USD", + ), + class_name="w-full flex flex-col gap-y-2 relative", + ), + # Accounts + rx.el.div( + # From Account + rx.el.div( + rx.el.p( + "From Account", + class_name="text-sm font-semibold text-foreground", + ), + rx.el.select( + rx.el.option( + "Main Checking (--8402) — $12,450.00", value="checking" + ), + rx.el.option( + "Business Account (--3301) — $5,200.00", value="business" + ), + class_name=( + "w-full rounded-radius border border-input bg-secondary " + "px-3 py-2 text-sm text-foreground appearance-none cursor-pointer " + "focus:outline-none focus:ring-1 focus:ring-input" + ), + ), + class_name="w-full flex flex-col gap-y-2", + ), + # To Account + rx.el.div( + rx.el.p( + "To Account", class_name="text-sm font-semibold text-foreground" + ), + rx.el.select( + rx.el.option( + "High Yield Savings (··1192) — $42,100.00", value="savings" + ), + rx.el.option("Roth IRA (··7745) — $18,300.00", value="ira"), + class_name=( + "w-full rounded-radius border border-input bg-secondary " + "px-3 py-2 text-sm text-foreground appearance-none cursor-pointer " + "focus:outline-none focus:ring-1 focus:ring-input" + ), + ), + class_name="w-full flex flex-col gap-y-2", + ), + class_name="flex flex-col gap-y-3", + ), + class_name="flex flex-col gap-y-4", + ), + # Summary Table + rx.el.div( + *[ + rx.el.div( + rx.el.p( + row["label"], + class_name=f"text-sm text-muted-foreground {'font-semibold text-foreground' if row['bold'] else 'font-light'}", + ), + rx.el.p( + row["value"], + class_name=f"text-sm {'font-bold text-foreground' if row['bold'] else 'font-medium text-foreground'}", + ), + class_name="w-full flex flex-row items-center justify-between py-2.5 border-b border-input last:border-b-0", + ) + for row in summary_rows + ], + class_name="w-full flex flex-col rounded-radius border border-input px-3", + ), + rx.el.div( + button("Confirm Transfer", variant="default", class_name="w-full"), + class_name="w-full", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="Finance") +def card_six() -> rx.Component: + return rx.el.div( + rx.el.div( + hi("CreditCardIcon", class_name="size-6 text-foreground"), + class_name="p-3 rounded-xl bg-secondary flex items-center justify-center w-fit", + ), + rx.el.div( + rx.el.p("Connect Bank", class_name="text-lg font-semibold text-foreground"), + rx.el.p( + "Link your payout method to receive monthly royalty distributions automatically.", + class_name="text-sm font-light text-muted-foreground text-center w-full max-w-[220px]", + ), + class_name="flex flex-col items-center gap-y-1", + ), + rx.el.div( + button("Set Up Payouts", variant="default", class_name="px-6 w-full"), + class_name="w-full", + ), + class_name="w-full flex flex-col gap-y-card items-center justify-center text-foreground", + ) + + +@masonry_card(label="Finance") +def card_seven() -> rx.Component: + summary_rows = [ + {"label": "Net Royalties", "value": "$0.00", "bold": False, "divider": False}, + {"label": "Processing Fee", "value": "-$0.00", "bold": False, "divider": True}, + { + "label": "Total Ready to Claim", + "value": "$0.00 USD", + "bold": True, + "divider": False, + }, + ] + + return rx.el.div( + # Top section — balance + status badge + rx.el.div( + rx.el.div( + rx.el.div( + rx.el.p( + "Claimable Balance", + class_name="text-sm font-light text-muted-foreground", + ), + rx.el.p( + "$0.00", + class_name="text-5xl font-bold tracking-tight text-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + # Pending Setup badge + rx.el.div( + rx.el.div(class_name="size-2 rounded-full bg-yellow-400 shrink-0"), + rx.el.p( + "Pending Setup", + class_name="text-xs font-medium text-foreground", + ), + class_name="flex flex-row items-center gap-x-2 px-3 py-1.5 rounded-radius border border-input bg-secondary w-fit", + ), + class_name="w-full flex flex-col gap-y-3", + ), + class_name="flex flex-col gap-y-1", + ), + # Summary table + rx.el.div( + *[ + rx.el.div( + rx.el.div( + rx.el.p( + row["label"], + class_name="text-sm text-muted-foreground font-light", + ), + rx.el.p( + row["value"], + class_name=f"text-sm {'font-bold text-foreground' if row['bold'] else 'font-medium text-foreground'}", + ), + class_name="w-full flex flex-row items-center justify-between py-2.5", + ), + rx.el.div(class_name="w-full h-px bg-input") + if row["divider"] + else rx.fragment(), + class_name="w-full flex flex-col", + ) + for row in summary_rows + ], + class_name="w-full flex flex-col bg-secondary rounded-radius px-3", + ), + # Footer note + rx.el.div( + rx.el.p( + "Once your bank is connected, balances over $10.00 are automatically " + "eligible for monthly distribution on the 15th of each month.", + class_name="text-sm font-light text-muted-foreground leading-relaxed", + ), + class_name="w-full", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="Finance") +def card_eight() -> rx.Component: + return rx.el.div( + # Header + rx.el.div( + rx.el.p( + "Set a new milestone", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "Define your financial target and we'll help you pace your savings.", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="w-full flex flex-col gap-y-1", + ), + # Form Body + rx.el.div( + # Goal Name + rx.el.div( + rx.el.p( + "Goal Name", class_name="text-sm font-semibold text-foreground" + ), + input( + placeholder="e.g. New Car, Home Downpayment", + class_name="!bg-secondary", + ), + class_name="w-full flex flex-col gap-y-2", + ), + # Target Amount + Target Date side by side + rx.el.div( + rx.el.div( + rx.el.p( + "Target Amount", + class_name="text-sm font-semibold text-foreground", + ), + input(placeholder="$15,000", class_name="!bg-secondary"), + class_name="flex flex-col gap-y-2 flex-1", + ), + rx.el.div( + rx.el.p( + "Target Date", + class_name="text-sm font-semibold text-foreground", + ), + input(placeholder="Dec 2025", class_name="!bg-secondary"), + class_name="flex flex-col gap-y-2 flex-1", + ), + class_name="w-full flex flex-row items-start gap-x-3", + ), + class_name="flex flex-col gap-y-3", + ), + # Buttons + rx.el.div( + button("Create Goal", variant="default", class_name="w-full"), + button("Cancel", variant="secondary", class_name="w-full"), + class_name="flex flex-col gap-y-2 w-full", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="Finance") +def card_nine() -> rx.Component: + savings_goals = [ + { + "category": "RETIREMENT", + "target": "$420,000", + "percent": 65, + "achieved": "$273,000", + }, + { + "category": "REAL ESTATE", + "target": "$85,000", + "percent": 32, + "achieved": "$27,200", + }, + ] + + def goal_item(goal: dict) -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.p( + goal["category"], + class_name="text-xs font-semibold tracking-widest text-muted-foreground uppercase", + ), + rx.el.p( + goal["target"], + class_name="text-4xl font-bold tracking-tight text-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + # Progress bar + rx.el.div( + rx.el.div( + class_name="h-full bg-primary rounded-radius", + style={"width": f"{goal['percent']}%"}, + ), + class_name="w-full h-1 bg-input rounded-radius overflow-hidden", + ), + # Percent + amount row + rx.el.div( + rx.el.p( + f"{goal['percent']}% achieved", + class_name="text-sm font-light text-muted-foreground", + ), + rx.el.p( + goal["achieved"], + class_name="text-sm font-medium text-foreground", + ), + class_name="w-full flex flex-row items-center justify-between", + ), + class_name="w-full flex flex-col gap-y-3 bg-secondary rounded-radius p-4", + ) + + return rx.el.div( + # Header + rx.el.div( + rx.el.div( + rx.el.p( + "Savings Targets", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "Active milestones for 2024", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-0.5", + ), + button("New Goal", variant="outline", size="sm"), + class_name="w-full flex flex-row items-start justify-between", + ), + # Goal items + rx.el.div( + *[goal_item(g) for g in savings_goals], + class_name="w-full flex flex-col gap-y-3", + ), + # Footer note + rx.el.div( + rx.el.p( + "You have not met your targets for this year.", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="w-full border-t border-input pt-3", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="General") +def card_ten() -> rx.Component: + social_fields = [ + { + "label": "Spotify Artist URL", + "icon": "SpotifyIcon", + "placeholder": "spotify.com/artist/3j...2k", + "value": "spotify.com/artist/3j...2k", + }, + { + "label": "Instagram Handle", + "icon": "InstagramIcon", + "placeholder": "@julianduryea_music", + "value": "@julianduryea_music", + }, + { + "label": "SoundCloud URL", + "icon": "SoundcloudIcon", + "placeholder": "soundcloud.com/username", + "value": "", + }, + { + "label": "Website", + "icon": "InternetIcon", + "placeholder": "https://yoursite.com", + "value": "", + }, + ] + + return rx.el.div( + # Header + rx.el.div( + rx.el.p("Social Links", class_name="text-lg font-semibold text-foreground"), + rx.el.p( + "Connect your platforms", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + # Fields + rx.el.div( + *[ + rx.el.div( + rx.el.p( + field["label"], + class_name="text-sm font-semibold text-foreground", + ), + input_with_addons( + placeholder=field["placeholder"], + prefix=hi( + field["icon"], class_name="size-4 text-muted-foreground" + ), + class_name="!bg-secondary pl-2", + ), + class_name="w-full flex flex-col gap-y-2", + ) + for field in social_fields + ], + class_name="w-full flex flex-col gap-y-3", + ), + # Buttons + rx.el.div( + button("Discard", variant="secondary", size="sm"), + button("Save Changes", variant="default", size="sm"), + class_name="w-full flex flex-row items-center justify-end gap-x-2", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +def codespaces_panel() -> rx.Component: + return rx.el.div( + # Section header + rx.el.div( + rx.el.div( + rx.el.p( + "Codespaces", class_name="text-sm font-semibold text-foreground" + ), + rx.el.p( + "Your workspaces in the cloud", + class_name="text-xs font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-0.5", + ), + rx.el.div( + button( + hi("Add01Icon", class_name="size-4"), + variant="ghost", + size="sm", + class_name="!px-2 !py-2", + ), + button( + hi("MoreHorizontalIcon", class_name="size-4"), + variant="ghost", + size="sm", + class_name="!px-2 !py-2", + ), + class_name="flex flex-row items-center gap-x-1", + ), + class_name="w-full flex flex-row items-start justify-between", + ), + rx.el.div(class_name="w-full h-px bg-input"), + # Empty state + rx.el.div( + rx.el.div( + hi("Database01Icon", class_name="size-5 text-muted-foreground"), + class_name="p-3 rounded-xl bg-secondary flex items-center justify-center", + ), + rx.el.p( + "No codespaces", class_name="text-sm font-semibold text-foreground" + ), + rx.el.p( + "You don't have any codespaces with this repository checked out", + class_name="text-xs font-light text-muted-foreground text-center max-w-[200px]", + ), + button("Create Codespace", variant="default", size="sm"), + rx.el.p( + "Learn more about codespaces", + class_name="text-xs text-muted-foreground underline underline-offset-2 cursor-pointer", + ), + class_name="w-full flex flex-col items-center gap-y-3 py-4", + ), + rx.el.div(class_name="w-full h-px bg-input"), + # Footer + rx.el.p( + "Codespace usage for this repository is paid for by SquidDip", + class_name="text-xs font-light text-muted-foreground pt-1 text-center", + ), + class_name="w-full flex flex-col gap-y-3", + ) + + +def local_panel() -> rx.Component: + return rx.el.div( + # Nested sub-tabs: HTTPS / SSH / GitHub CLI + tabs.root( + tabs.list( + tabs.indicator(), + tabs.tab("HTTPS", value="https"), + tabs.tab("SSH", value="ssh"), + tabs.tab("GitHub CLI", value="cli"), + class_name="w-full border-b border-input/60 pb-1", + ), + # HTTPS panel + tabs.panel( + rx.el.div( + rx.el.div( + input_with_addons( + value="https://github.com/shadcn-ui/ui.git", + placeholder="", + suffix=hi( + "Copy01Icon", + class_name="size-4 text-muted-foreground cursor-pointer", + ), + class_name="!bg-secondary text-xs", + ), + rx.el.p( + "Clone using the web URL.", + class_name="text-xs font-light text-muted-foreground px-1", + ), + class_name="w-full flex flex-col gap-y-2 border border-input rounded-radius p-2", + ), + class_name="w-full pt-3", + ), + value="https", + class_name="w-full", + ), + # SSH panel + tabs.panel( + rx.el.div( + input_with_addons( + value="git@github.com:shadcn-ui/ui.git", + placeholder="", + suffix=hi( + "Copy01Icon", + class_name="size-4 text-muted-foreground cursor-pointer", + ), + class_name="!bg-secondary text-xs", + ), + class_name="w-full pt-3", + ), + value="ssh", + class_name="w-full", + ), + # GitHub CLI panel + tabs.panel( + rx.el.div( + input_with_addons( + value="gh repo clone shadcn-ui/ui", + placeholder="", + suffix=hi( + "Copy01Icon", + class_name="size-4 text-muted-foreground cursor-pointer", + ), + class_name="!bg-secondary text-xs", + ), + class_name="w-full pt-3", + ), + value="cli", + class_name="w-full", + ), + default_value="https", + class_name="w-full flex flex-col", + ), + rx.el.div(class_name="w-full h-px bg-input"), + # Action links + rx.el.div( + rx.el.div( + hi("ComputerIcon", class_name="size-4 text-foreground"), + rx.el.p( + "Open with GitHub Desktop", + class_name="text-sm font-semibold text-foreground", + ), + class_name="flex flex-row items-center gap-x-3 cursor-pointer hover:opacity-70 transition-opacity", + ), + rx.el.div( + hi("Download04Icon", class_name="size-4 text-foreground"), + rx.el.p( + "Download ZIP", + class_name="text-sm font-semibold text-foreground", + ), + class_name="flex flex-row items-center gap-x-3 cursor-pointer hover:opacity-70 transition-opacity", + ), + class_name="w-full flex flex-col gap-y-3 pt-1", + ), + class_name="w-full flex flex-col gap-y-3", + ) + + +@masonry_card(label="Tech") +def card_eleven() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.p( + "Development Environment", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "Manage your cloud and local workspaces", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + tabs.root( + tabs.list( + tabs.indicator(), + tabs.tab("Codespaces", value="codespaces"), + tabs.tab("Local", value="local"), + class_name="w-full", + ), + rx.el.div( + tabs.panel( + codespaces_panel(), value="codespaces", class_name="w-full pt-3" + ), + tabs.panel(local_panel(), value="local", class_name="w-full pt-3"), + class_name="w-full", + ), + default_value="codespaces", + class_name="w-full flex flex-col", + ), + class_name="w-full flex flex-col gap-y-card items-stretch justify-start text-foreground overflow-hidden", + ) + + +@masonry_card(label="General") +def card_twelve() -> rx.Component: + toggle_rows = [ + { + "label": "Public Statistics", + "description": "Allow others to see your total stream count and listening activity", + "default": True, + }, + { + "label": "Email Notifications", + "description": "Monthly royalty reports and distribution updates", + "default": True, + }, + ] + + return rx.el.div( + # Header + rx.el.div( + rx.el.div( + rx.el.p( + "Preferences", class_name="text-lg font-semibold text-foreground" + ), + rx.el.div( + hi("Cancel01Icon", class_name="size-3.5 text-foreground"), + class_name="p-1 flex items-center justify-center bg-secondary rounded-radius", + ), + class_name="w-full flex flex-row items-center justify-between", + ), + rx.el.p( + "Manage your account settings and notifications.", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="w-full flex flex-col gap-y-1 justify-start", + ), + # Form Body + rx.el.div( + # Default Currency + rx.el.div( + rx.el.p( + "Default Currency", + class_name="text-sm font-semibold text-foreground", + ), + rx.el.select( + rx.el.option("USD — United States Dollar", value="usd"), + rx.el.option("EUR — Euro", value="eur"), + rx.el.option("GBP — British Pound", value="gbp"), + class_name=( + "w-full rounded-radius border border-input bg-secondary " + "px-3 py-2 text-sm text-foreground appearance-none cursor-pointer " + "focus:outline-none focus:ring-1 focus:ring-input" + ), + ), + class_name="w-full flex flex-col gap-y-2", + ), + rx.el.div(class_name="w-full h-px bg-input my-2"), + # Toggle rows + rx.el.div( + *[ + rx.el.div( + rx.el.div( + rx.el.p( + row["label"], + class_name="text-sm font-semibold text-foreground", + ), + rx.el.p( + row["description"], + class_name="text-xs font-light text-muted-foreground leading-relaxed", + ), + class_name="flex flex-col gap-y-0.5 flex-1", + ), + switch(default_checked=row["default"]), + class_name="w-full flex flex-row items-start justify-between gap-x-4 py-3 border-b border-input last:border-b-0", + ) + for row in toggle_rows + ], + class_name="w-full flex flex-col", + ), + class_name="flex flex-col gap-y-1", + ), + # Buttons + rx.el.div( + button("Reset", variant="outline", size="sm"), + button("Save Preferences", variant="default", size="sm"), + class_name="w-full flex flex-row items-center justify-between pt-2", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="General") +def card_thirteen() -> rx.Component: + notification_items = [ + { + "label": "Transaction alerts", + "description": "Deposits, withdrawals, and transfers.", + "default": True, + }, + { + "label": "Security alerts", + "description": "Login attempts and account changes.", + "default": True, + }, + { + "label": "Goal milestones", + "description": "Updates at 25%, 50%, 75%, and 100%.", + "default": False, + }, + { + "label": "Market updates", + "description": "Daily portfolio summary and price alerts.", + "default": False, + }, + ] + + def checkbox_row(item: dict) -> rx.Component: + return rx.el.label( + checkbox.root( + checkbox.indicator(), + default_checked=item["default"], + class_name="mt-1", + ), + rx.el.div( + rx.el.p( + item["label"], class_name="text-sm font-semibold text-foreground" + ), + rx.el.p( + item["description"], + class_name="text-xs font-light text-muted-foreground", + ), + class_name="flex flex-col", + ), + class_name="flex flex-row items-start gap-x-3 cursor-pointer", + ) + + return rx.el.div( + # Header + rx.el.div( + rx.el.p( + "Notifications", class_name="text-lg font-semibold text-foreground" + ), + rx.el.p( + "Choose what you want to be notified about.", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="w-full flex flex-col gap-y-1", + ), + # Body + rx.el.div( + rx.el.div(class_name="w-full h-px bg-input mb-4"), + # Individual checkboxes + rx.el.div( + *[checkbox_row(item) for item in notification_items], + class_name="w-full flex flex-col gap-y-4", + ), + class_name="flex flex-col", + ), + # Save button + rx.el.div( + button("Save Preferences", variant="default", class_name="w-full"), + class_name="w-full pt-2", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="Healthcare") +def card_fourteen() -> rx.Component: + days = [ + {"label": "M", "calories": 75, "load": 60}, + {"label": "T", "calories": 30, "load": 25}, + {"label": "W", "calories": 55, "load": 45}, + {"label": "T", "calories": 45, "load": 35}, + {"label": "F", "calories": 80, "load": 65}, + {"label": "S", "calories": 25, "load": 20}, + {"label": "S", "calories": 40, "load": 30}, + ] + + def day_bar(day: dict) -> rx.Component: + return rx.el.div( + rx.el.p( + day["label"], class_name="text-sm font-medium text-muted-foreground" + ), + # Bar track + rx.el.div( + # Grey background portion (remaining capacity) + rx.el.div( + class_name="w-full bg-secondary rounded-radius", + style={"height": f"{100 - day['calories']}%"}, + ), + # Green filled portion + rx.el.div( + class_name="w-full bg-chart-1 rounded-radius", + style={"height": f"{day['calories']}%"}, + ), + class_name="w-full flex-1 flex flex-col justify-end gap-y-0.5 overflow-hidden", + ), + class_name="flex flex-col items-center gap-y-2 border border-input rounded-radius p-2 flex-1 h-36", + ) + + return rx.el.div( + # Header + rx.el.div( + rx.el.p( + "Weekly Fitness Summary", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "Calories and workout load by day", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="w-full flex flex-col gap-y-1", + ), + # Bar chart row + rx.el.div( + *[day_bar(day) for day in days], + class_name="w-full flex flex-row items-stretch gap-x-1.5", + ), + # View details button + rx.el.div( + button( + "View details", + variant="default", + class_name="w-full", + ), + class_name="w-full pt-2", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="Healthcare") +def card_fifteen() -> rx.Component: + sleep_groups = [ + { + "value": "2h 10m", + "label": "Deep", + "bars": [40, 35], + "colors": ["bg-chart-1", "bg-chart-2"], + }, + { + "value": "3h 48m", + "label": "Light", + "bars": [75, 65, 80], + "colors": ["bg-chart-2", "bg-chart-1", "bg-chart-3"], + }, + { + "value": "1h 26m", + "label": "REM", + "bars": [70, 55, 60], + "colors": ["bg-chart-1", "bg-chart-2", "bg-chart-3"], + }, + { + "value": "84", + "label": "Score", + "bars": [72, 45], + "colors": ["bg-chart-1", "bg-chart-3"], + }, + ] + + legend_items = [ + {"label": "Deep", "color": "bg-chart-1"}, + {"label": "Light", "color": "bg-chart-2"}, + {"label": "REM", "color": "bg-chart-3"}, + ] + + def bar_column(height: int, color: str) -> rx.Component: + return rx.el.div( + class_name=f"w-5 rounded-radius {color}", + style={"height": f"{height}%"}, + ) + + def sleep_group(group: dict) -> rx.Component: + return rx.el.div( + # Bars + rx.el.div( + *[bar_column(h, c) for h, c in zip(group["bars"], group["colors"])], + class_name="flex flex-row items-end gap-x-0.5 h-28", + ), + # Label + rx.el.div( + rx.el.p( + group["value"], class_name="text-sm font-semibold text-foreground" + ), + rx.el.p( + group["label"], + class_name="text-xs font-light text-muted-foreground", + ), + class_name="flex flex-col items-start gap-y-0.5", + ), + class_name="flex flex-col items-start gap-y-2 flex-1", + ) + + return rx.el.div( + # Header + rx.el.div( + rx.el.p("Sleep Report", class_name="text-lg font-semibold text-foreground"), + rx.el.p( + "Last night · 7h 24m", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="w-full flex flex-col gap-y-1", + ), + # Body + rx.el.div( + # Bar groups + rx.el.div( + *[sleep_group(g) for g in sleep_groups], + class_name="w-full flex flex-row items-end gap-x-3", + ), + # Legend + rx.el.div( + *[ + rx.el.div( + rx.el.div( + class_name=f"size-2 rounded-full {item['color']} shrink-0" + ), + rx.el.p( + item["label"], + class_name="text-xs font-light text-muted-foreground", + ), + class_name="flex flex-row items-center gap-x-1.5", + ) + for item in legend_items + ], + class_name="w-full flex flex-row items-center gap-x-4 pt-2", + ), + class_name="flex flex-col gap-y-4", + ), + rx.el.div(class_name="w-full h-px bg-input"), + # Footer + rx.el.div( + button("Good", variant="outline", size="sm", class_name="rounded-full"), + button("Details", variant="outline", size="sm"), + class_name="w-full flex flex-row items-center justify-between pt-2", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="General") +def card_sixteen() -> rx.Component: + members = [ + {"email": "alex@example.com", "role": "editor"}, + {"email": "sam@example.com", "role": "viewer"}, + ] + + def member_row(member: dict) -> rx.Component: + return rx.el.div( + input( + default_value=member["email"], + class_name="flex-1", + ), + class_name="w-full flex flex-row items-center gap-x-2", + ) + + return rx.el.div( + # Header + rx.el.div( + rx.el.p("Invite Team", class_name="text-lg font-semibold text-foreground"), + rx.el.p( + "Add members to your workspace", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="w-full flex flex-col gap-y-1", + ), + # Member rows + rx.el.div( + rx.el.div( + *[member_row(m) for m in members], + class_name="w-full flex flex-col gap-y-2", + ), + # Add another + button( + hi("PlusSignIcon", class_name="size-3.5"), + "Add another", + variant="outline", + class_name="w-full", + ), + class_name="flex flex-col gap-y-3", + ), + # Divider + rx.el.div(class_name="w-full h-px bg-input"), + # Invite link + rx.el.div( + rx.el.p( + "Or share invite link", + class_name="text-sm font-semibold text-foreground", + ), + input_with_addons( + default_value="https://app.co/invite/x8f2k", + suffix=hi("Copy01Icon", class_name="size-4 text-muted-foreground mr-2"), + ), + class_name="w-full flex flex-col gap-y-2", + ), + # Send button + rx.el.div( + button("Send Invites", class_name="w-full"), + class_name="w-full pt-2", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="General") +def card_seventeen() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.p( + "404 - Not Found", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "The page you're looking for doesn't exist. Try searching for what you need below.", + class_name="text-sm font-light text-muted-foreground text-center", + ), + class_name="flex flex-col items-center gap-y-1", + ), + # Search input + rx.el.div( + input_with_addons( + placeholder="example.com", + ), + class_name="w-full max-w-md mx-auto", + ), + # Go to homepage + rx.el.div( + rx.el.button( + "Go to homepage", + class_name="text-sm font-semibold text-foreground hover:opacity-70 transition-opacity cursor-pointer bg-transparent border-none", + ), + class_name="flex justify-center pt-2", + ), + class_name="w-full flex flex-col gap-y-card items-center justify-center text-foreground min-h-48", + ) + + +@masonry_card(label="Healthcare") +def card_eighteen() -> rx.Component: + times = ["9:00 AM", "10:30 AM", "11:00 AM", "1:30 PM"] + + return rx.el.div( + # Header + rx.el.div( + rx.el.p( + "Book Appointment", class_name="text-lg font-semibold text-foreground" + ), + rx.el.p( + "Dr. Sarah Chen · Cardiology", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="w-full flex flex-col gap-y-1", + ), + # Time slots + rx.el.div( + rx.el.p( + "Available on March 18, 2026", + class_name="text-sm font-semibold text-foreground", + ), + rx.el.div( + *[button(t, size="sm", variant="secondary") for t in times], + class_name="w-full flex flex-row flex-wrap gap-2", + ), + class_name="w-full flex flex-col gap-y-3", + ), + # Note box + rx.el.div( + rx.el.p("New patient?", class_name="text-sm font-semibold text-foreground"), + rx.el.p( + "Please arrive 15 minutes early.", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="w-full flex flex-col gap-y-1 border border-input rounded-radius p-3", + ), + rx.el.div( + button("Book Appointment", class_name="w-full"), + class_name="w-full pt-2", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="Finance") +def card_nineteen() -> rx.Component: + items = [ + { + "title": "Change transfer limit", + "description": "Adjust how much you can send from your balance.", + }, + { + "title": "Scheduled transfers", + "description": "Set up a transfer to send at a later date.", + }, + { + "title": "Direct Debits", + "description": "Set up and manage regular payments.", + }, + { + "title": "Recurring card payments", + "description": "Manage your repeated card transactions.", + }, + ] + + def menu_item(item: dict) -> rx.Component: + return button( + rx.el.div( + rx.el.div( + rx.el.p( + item["title"], + class_name="text-sm font-semibold text-foreground text-left", + ), + rx.el.p( + item["description"], + class_name="text-sm font-light text-muted-foreground text-left whitespace-normal", + ), + class_name="flex flex-col gap-y-0.5 flex-1", + ), + class_name="w-full flex flex-row items-center gap-x-3", + ), + variant="secondary", + class_name="w-full h-auto py-3 px-4", + ) + + return rx.el.div( + rx.el.div( + rx.el.p( + "Account Controls", class_name="text-lg font-semibold text-foreground" + ), + rx.el.p( + "Limits and recurring payments", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + rx.el.div( + *[menu_item(i) for i in items], + class_name="w-full flex flex-col gap-y-2", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="General") +def card_twenty() -> rx.Component: + colors = [ + {"bg": "bg-background", "label": "-background", "border": True}, + {"bg": "bg-foreground", "label": "-foreground"}, + {"bg": "bg-primary", "label": "-primary"}, + {"bg": "bg-secondary", "label": "-secondary", "border": True}, + {"bg": "bg-muted", "label": "-muted", "border": True}, + {"bg": "bg-accent", "label": "-accent", "border": True}, + {"bg": "bg-border", "label": "-border", "border": True}, + {"bg": "bg-chart-1", "label": "-chart-1"}, + {"bg": "bg-chart-2", "label": "-chart-2"}, + {"bg": "bg-chart-3", "label": "-chart-3"}, + {"bg": "bg-chart-4", "label": "-chart-4"}, + {"bg": "bg-chart-5", "label": "-chart-5"}, + ] + + def swatch(color: dict) -> rx.Component: + border_class = "border border-input" if color.get("border") else "" + return rx.el.div( + rx.el.div( + class_name=f"rounded-radius {color['bg']} {border_class} size-12 flex-shrink-0", + ), + # Updated class_name below + rx.el.p( + color["label"], + class_name="text-xs text-muted-foreground font-theme truncate w-full text-center", + ), + # Added min-w-0 to allow the flex item to shrink + class_name="flex flex-col items-center gap-y-2 flex-1 min-w-0", + ) + + rows = [colors[:6], colors[6:]] + + return rx.el.div( + rx.el.div( + rx.el.p( + "Theme Colors", + class_name="text-lg font-semibold text-foreground text-center", + ), + rx.el.p( + "Dynamic palette variants", + class_name="text-sm font-light text-muted-foreground text-center", + ), + class_name="flex flex-col gap-y-1", + ), + rx.el.div( + *[ + rx.el.div( + *[swatch(c) for c in row], + class_name="w-full flex flex-row gap-x-2", + ) + for row in rows + ], + class_name="flex flex-col gap-y-4", + ), + class_name="w-full flex flex-col gap-y-card", + ) + + +@masonry_card(label="General") +def card_twenty_one() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.p( + selected_font_cs.value, + class_name="text-xs font-light text-muted-foreground tracking-widest uppercase font-theme", + ), + rx.el.p( + "Fellowship of the Ring.", + class_name="text-3xl font-bold text-foreground leading-tight font-theme", + ), + class_name="flex flex-col gap-y-1", + ), + rx.el.div( + rx.el.p( + """Five hundred times have the red leaves fallen in Mirkwood in my home since then," said Legolas, "and but a little while does that seem to us.""", + class_name="text-sm font-light text-muted-foreground leading-relaxed font-theme", + ), + rx.el.p( + """I have seen many an oak grow from acorn to ruinous age. I wish that there were leisure now to walk among them: they have voices, and in time I might come to understand their thought.""", + class_name="text-sm font-light text-muted-foreground leading-relaxed font-theme", + ), + class_name="flex flex-col gap-y-2", + ), + rx.el.div( + button( + "Share Feedback", + variant="outline", + class_name="w-full rounded-full", + ), + class_name="w-full pt-2", + ), + class_name="w-full flex flex-col gap-y-card text-foreground", + ) + + +@masonry_card(label="Finance") +def card_twenty_two() -> rx.Component: + ledger_data = [ + { + "id": "#TR-8402", + "date": "May 28, 2026", + "status": "Completed", + "method": "ACH Transfer", + "amount": "+ $4,250.00", + }, + { + "id": "#TR-3301", + "date": "May 27, 2026", + "status": "Pending", + "method": "Card Payment", + "amount": "- $120.50", + }, + { + "id": "#TR-1192", + "date": "May 25, 2026", + "status": "Completed", + "method": "Wire", + "amount": "+ $12,000.00", + }, + { + "id": "#TR-7745", + "date": "May 24, 2026", + "status": "Failed", + "method": "ACH Transfer", + "amount": "- $2,500.00", + }, + ] + + columns = [ + { + "header": "Transaction ID", + "accessor": "id", + "class_name": "font-mono text-xs", + }, + {"header": "Date", "accessor": "date"}, + {"header": "Status", "accessor": "status"}, + { + "header": "Amount", + "accessor": "amount", + "align": "right", + "class_name": "font-mono font-semibold", + }, + ] + + def status_badge(status: str) -> rx.Component: + variant = "default" + if status == "Completed": + variant = "default" + elif status == "Pending": + variant = "secondary" + elif status == "Failed": + variant = "destructive" + return badge(status, variant=variant) + + return rx.el.div( + rx.el.div( + rx.el.p( + "Financial Ledger", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "Internal revenue tracking", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + table.root( + table.header( + table.row( + *[ + table.head( + col["header"], + class_name=cn( + "text-right" if col.get("align") == "right" else "" + ), + ) + for col in columns + ], + class_name="border-b border-input", + ) + ), + table.body( + *[ + table.row( + table.cell( + row["id"], + class_name="font-theme text-xs text-muted-foreground text-nowrap", + ), + table.cell( + row["date"], + class_name="text-nowrap font-theme text-sm font-light text-foreground", + ), + table.cell(status_badge(row["status"])), + table.cell( + row["amount"], + class_name=cn( + "text-right font-theme font-medium text-nowrap", + "text-emerald-400" + if "+" in row["amount"] + else "text-destructive", + ), + ), + ) + for row in ledger_data + ], + class_name="divide-y divide-input", + ), + striped=True, + class_name="border-none shadow-none", + ), + class_name="w-full flex flex-col gap-y-card border-none font-theme", + ) + + +@masonry_card(label="Tech") +def card_twenty_three() -> rx.Component: + inventory_data = [ + { + "name": "production-db-01", + "type": "db.m5.large", + "region": "us-east-1", + "load": 42, + }, + { + "name": "api-gateway-v2", + "type": "t3.medium", + "region": "eu-central-1", + "load": 78, + }, + { + "name": "worker-node-x", + "type": "c5.xlarge", + "region": "us-west-2", + "load": 15, + }, + { + "name": "auth-service-pod", + "type": "t3.small", + "region": "ap-southeast-1", + "load": 92, + }, + ] + + return rx.el.div( + rx.el.div( + rx.el.p( + "Infrastructure Inventory", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "Track internal inventory", + class_name="text-sm text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + table.root( + table.header( + table.row( + table.head("Instance Name"), + table.head("Type"), + table.head("Load", class_name="text-right"), + class_name="border-b border-input", + ), + ), + table.body( + *[ + table.row( + table.cell( + rx.el.div( + rx.el.p( + row["name"], + class_name="text-sm font-medium text-nowrap text-foreground", + ), + class_name="flex flex-row items-center gap-x-2", + ) + ), + table.cell( + row["type"], class_name="text-xs text-muted-foreground" + ), + table.cell( + rx.el.div( + rx.el.div( + class_name=cn( + "h-full rounded-full transition-all", + "bg-emerald-500" + if row["load"] < 50 + else "bg-yellow-500" + if row["load"] < 80 + else "bg-destructive", + ), + style={"width": f"{row['load']}%"}, + ), + class_name="w-20 h-1.5 bg-secondary rounded-full overflow-hidden ml-auto", + ), + class_name="text-right", + ), + ) + for row in inventory_data + ], + class_name="divide-y divide-input", + ), + class_name="border-none shadow-none", + ), + rx.el.div( + button("Refresh Status", variant="secondary", class_name="w-full"), + class_name="w-full pt-2", + ), + class_name="w-full flex flex-col gap-y-card font-theme", + ) + + +@masonry_card(label="Healthcare") +def card_twenty_four() -> rx.Component: + patients = [ + { + "name": "Sarah Jenkins", + "id": "P-1002", + "vitals": "Stable", + "last_check": "2h ago", + }, + { + "name": "Michael Chen", + "id": "P-1045", + "vitals": "Critical", + "last_check": "15m ago", + }, + { + "name": "Elena Rodriguez", + "id": "P-1102", + "vitals": "Stable", + "last_check": "5h ago", + }, + { + "name": "James Wilson", + "id": "P-0982", + "vitals": "Observation", + "last_check": "1h ago", + }, + ] + + return rx.el.div( + rx.el.div( + rx.el.div( + rx.el.p( + "Patient Registry", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "Unit 4 - North Wing", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + badge( + "Active Shift", + variant="outline", + class_name="text-[10px] uppercase tracking-wider", + ), + class_name="w-full flex flex-row items-start justify-between", + ), + table.root( + table.header( + table.row( + table.head("Patient"), + table.head("Vitals"), + table.head("Last Check"), + class_name="border-b border-input", + ), + ), + table.body( + *[ + table.row( + table.cell( + rx.el.div( + rx.el.p( + row["name"], + class_name="text-sm font-medium text-foreground font-theme", + ), + rx.el.p( + row["id"], + class_name="text-xs text-muted-foreground font-theme", + ), + class_name="flex flex-col", + ), + class_name="text-nowrap", + ), + table.cell( + rx.el.div( + rx.el.div( + class_name=cn( + "size-2 rounded-full", + "bg-emerald-500" + if row["vitals"] == "Stable" + else "bg-destructive" + if row["vitals"] == "Critical" + else "bg-yellow-500", + ) + ), + rx.el.p( + row["vitals"], class_name="text-xs text-foreground" + ), + class_name="flex flex-row items-center gap-x-2", + ) + ), + table.cell( + row["last_check"], + class_name="text-xs text-muted-foreground", + ), + ) + for row in patients + ], + class_name="divide-y divide-input", + ), + class_name="border-none shadow-none", + ), + class_name="w-full flex flex-col gap-y-card font-theme", + ) + + +@masonry_card(label="Tech") +def card_twenty_five() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.p( + "Key Performance Indicators", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "Analysis of current trends", + class_name="text-sm text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + rx.el.div( + metric( + label="Total Revenue", + value="$1.2M", + trend="+12.5%", + trend_direction="up", + class_name="border-none bg-secondary/50 shadow-none", + ), + metric( + label="Active Users", + value="42.5k", + trend="+3.1%", + trend_direction="up", + class_name="border-none bg-secondary/50 shadow-none", + ), + metric( + label="Churn Rate", + value="2.4%", + trend="-0.8%", + trend_direction="down", + class_name="border-none bg-secondary/50 shadow-none", + ), + class_name="grid grid-cols-1 gap-4", + ), + class_name="w-full flex flex-col gap-y-card font-theme", + ) + + +@masonry_card(label="Healthcare") +def card_twenty_six() -> rx.Component: + medication_data = [ + { + "med": "Lisinopril", + "dose": "10mg", + "freq": "Daily", + "status": "Administered", + "time": "08:00 AM", + }, + { + "med": "Metformin", + "dose": "500mg", + "freq": "BID", + "status": "Due", + "time": "06:00 PM", + }, + { + "med": "Atorvastatin", + "dose": "20mg", + "freq": "Nightly", + "status": "Scheduled", + "time": "09:00 PM", + }, + { + "med": "Amoxicillin", + "dose": "250mg", + "freq": "TID", + "status": "Administered", + "time": "02:00 PM", + }, + ] + + def status_badge(status: str) -> rx.Component: + variant = "default" + if status == "Administered": + variant = "default" + elif status == "Due": + variant = "outline" + elif status == "Scheduled": + variant = "secondary" + return badge(status, variant=variant) + + return rx.el.div( + rx.el.div( + rx.el.p( + "Medication Record", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "Daily administration schedule", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + table.root( + table.header( + table.row( + table.head("Medication"), + table.head("Dosage"), + table.head("Status"), + class_name="border-b border-input", + ), + ), + table.body( + *[ + table.row( + table.cell( + rx.el.div( + rx.el.p( + row["med"], + class_name="text-sm font-medium text-foreground", + ), + rx.el.p( + row["freq"], + class_name="text-xs text-muted-foreground", + ), + class_name="flex flex-col", + ) + ), + table.cell( + row["dose"], class_name="text-sm font-theme text-foreground" + ), + table.cell(status_badge(row["status"])), + ) + for row in medication_data + ], + class_name="divide-y divide-input", + ), + striped=True, + class_name="border-none shadow-none", + ), + class_name="w-full flex flex-col gap-y-card font-theme", + ) + + +@masonry_card(label="Healthcare") +def card_twenty_seven() -> rx.Component: + lab_results = [ + { + "test": "Glucose", + "value": "104", + "unit": "mg/dL", + "range": "70-99", + "flag": "High", + }, + { + "test": "Hemoglobin", + "value": "14.2", + "unit": "g/dL", + "range": "13.5-17.5", + "flag": "Normal", + }, + { + "test": "WBC Count", + "value": "6.8", + "unit": "x10E3/uL", + "range": "4.5-11.0", + "flag": "Normal", + }, + { + "test": "Potassium", + "value": "3.1", + "unit": "mmol/L", + "range": "3.5-5.1", + "flag": "Low", + }, + ] + + return rx.el.div( + rx.el.div( + rx.el.p( + "Laboratory Results", class_name="text-lg font-semibold text-foreground" + ), + rx.el.p( + "Recent metabolic panel", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + table.root( + table.header( + table.row( + table.head("Test"), + table.head("Result", class_name="text-right"), + table.head("Flag", class_name="text-center"), + class_name="border-b border-input", + ) + ), + table.body( + *[ + table.row( + table.cell( + rx.el.div( + rx.el.p( + row["test"], + class_name="text-sm font-medium text-foreground", + ), + rx.el.p( + f"Range: {row['range']}", + class_name="text-[10px] text-muted-foreground uppercase", + ), + class_name="flex flex-col", + ) + ), + table.cell( + rx.el.div( + rx.el.p( + row["value"], + class_name="text-sm font-semibold text-foreground", + ), + rx.el.p( + row["unit"], + class_name="text-xs text-muted-foreground", + ), + class_name="flex flex-col items-end", + ) + ), + table.cell( + rx.el.div( + badge( + row["flag"], + variant="destructive" + if row["flag"] in ["High", "Low"] + else "secondary", + class_name="text-[10px]", + ), + class_name="flex justify-center", + ) + ), + ) + for row in lab_results + ], + class_name="divide-y divide-input", + ), + class_name="border-none shadow-none", + ), + class_name="w-full flex flex-col gap-y-card font-theme", + ) + + +@masonry_card(label="Healthcare") +def card_twenty_eight() -> rx.Component: + vitals_log = [ + {"time": "12:00 PM", "bp": "120/80", "hr": "72", "temp": "98.6", "o2": "98%"}, + {"time": "08:00 AM", "bp": "118/75", "hr": "68", "temp": "98.2", "o2": "99%"}, + {"time": "04:00 AM", "bp": "125/82", "hr": "75", "temp": "98.9", "o2": "97%"}, + {"time": "12:00 AM", "bp": "115/70", "hr": "65", "temp": "98.1", "o2": "99%"}, + ] + + return rx.el.div( + rx.el.div( + rx.el.p( + "Vitals Monitoring", class_name="text-lg font-semibold text-foreground" + ), + rx.el.p( + "24-hour observation log", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + table.root( + table.header( + table.row( + table.head("Time"), + table.head("BP", class_name="text-right"), + table.head("HR", class_name="text-right"), + table.head("O2", class_name="text-right"), + class_name="border-b border-input", + ) + ), + table.body( + *[ + table.row( + table.cell( + row["time"], class_name="text-xs text-muted-foreground" + ), + table.cell( + row["bp"], + class_name="text-sm font-theme text-right text-foreground", + ), + table.cell( + row["hr"], + class_name="text-sm font-theme text-right text-foreground", + ), + table.cell( + row["o2"], + class_name="text-sm font-theme text-right text-emerald-500", + ), + ) + for row in vitals_log + ], + class_name="divide-y divide-input", + ), + class_name="border-none shadow-none", + ), + class_name="w-full flex flex-col gap-y-card font-theme", + ) + + +@masonry_card(label="Healthcare") +def card_twenty_nine() -> rx.Component: + resources = [ + {"dept": "Emergency", "staff": "12/15", "beds": "85%", "status": "Critical"}, + {"dept": "ICU", "staff": "8/8", "beds": "92%", "status": "Stable"}, + {"dept": "Radiology", "staff": "4/6", "beds": "N/A", "status": "Delayed"}, + {"dept": "Pediatrics", "staff": "10/12", "beds": "60%", "status": "Stable"}, + ] + + return rx.el.div( + rx.el.div( + rx.el.p( + "Department Capacity", + class_name="text-lg font-semibold text-foreground", + ), + rx.el.p( + "Real-time resource allocation", + class_name="text-sm font-light text-muted-foreground", + ), + class_name="flex flex-col gap-y-1", + ), + table.root( + table.header( + table.row( + table.head("Dept"), + table.head("Beds", class_name="text-center"), + table.head("Status", class_name="text-right"), + class_name="border-b border-input", + ) + ), + table.body( + *[ + table.row( + table.cell( + rx.el.div( + rx.el.p( + row["dept"], + class_name="text-sm font-medium text-foreground", + ), + rx.el.p( + f"Staff: {row['staff']}", + class_name="text-[10px] text-muted-foreground", + ), + class_name="flex flex-col", + ) + ), + table.cell( + row["beds"], + class_name="text-sm text-center text-foreground", + ), + table.cell( + rx.el.div( + badge( + row["status"], + variant="destructive" + if row["status"] == "Critical" + else "secondary" + if row["status"] == "Delayed" + else "default", + class_name="text-[10px]", + ), + class_name="flex justify-end", + ) + ), + ) + for row in resources + ], + class_name="divide-y divide-input", + ), + class_name="border-none shadow-none", + ), + class_name="w-full flex flex-col gap-y-card font-theme", + ) diff --git a/app/examples/utils.py b/app/examples/utils.py new file mode 100644 index 00000000..d3964cec --- /dev/null +++ b/app/examples/utils.py @@ -0,0 +1,42 @@ +import functools + +import reflex as rx + +from app.hooks import selected_component_category + + +def masonry_card(func=None, *, label="General"): + """ + Decorator to wrap a component function inside a standard + masonry-compatible div container with conditional visibility. + """ + if func is None: + return functools.partial(masonry_card, label=label) + + @functools.wraps(func) + def wrapper(*args, **kwargs): + inner_component = func(*args, **kwargs) + + return rx.el.div( + inner_component, + class_name=" ".join( + [ + "break-inside-avoid", + "w-full", + "bg-card", + "rounded-radius", + "border", + "border-input", + "p-card", + "flex flex-col", + ] + ) + + rx.cond( + (selected_component_category.value == "All") + | (selected_component_category.value == label), + " flex", + " hidden", + ).to(str), + ) + + return wrapper diff --git a/app/hooks.py b/app/hooks.py new file mode 100644 index 00000000..2274dbe1 --- /dev/null +++ b/app/hooks.py @@ -0,0 +1,52 @@ +from reflex.experimental import ClientStateVar + +import app.utils.routes as routes + +selected_base_color_cs = ClientStateVar.create("selected_base_color_cs", "Neutral") + +base_theme_color = ClientStateVar.create("base_theme_color", "oklch(0.708 0 0)") + +theme_color = ClientStateVar.create("theme_color", "") + +chart_color = ClientStateVar.create("chart_color", "") + +selected_theme_cs = ClientStateVar.create("selected_theme_cs", "Neutral") + +selected_chart_cs = ClientStateVar.create("selected_chart_cs", "Neutral") + +selected_style_cs = ClientStateVar.create("selected_style_cs", "Vega") + +selected_font_cs = ClientStateVar.create("selected_font_cs", "Inter") + +selected_radius_cs = ClientStateVar.create("selected_radius_cs", "Medium") + +theme = ClientStateVar.create("theme", {}) + +seed = ClientStateVar.create("seed", "") + +darkmode = ClientStateVar.create("darkmode", False) + +copy_preset_value = ClientStateVar.create("copy_preset_value", False) + +theme_export_method = ClientStateVar.create("theme_export_method", "local") + +theme_preset_option = ClientStateVar.create("theme_preset_option", "full") + +css_output = ClientStateVar.create("cssoutput", "") + +is_css_output_copied = ClientStateVar.create("is_css_output_copied", False) + +is_rxconfig_copied = ClientStateVar.create("is_rxconfig_copied", False) + +is_export_command_copied = ClientStateVar.create("is_export_command_copied", False) + +welcome_open = ClientStateVar.create("welcome_open", False) + +selected_component_category = ClientStateVar.create( + "selected_component_category", "All" +) + +search_items_cs = ClientStateVar.create( + "search_items_cs", + routes.GET_STARTED_URLS + routes.BASE_UI_COMPONENTS + routes.CHARTS_URLS, +) diff --git a/app/pages/charts.py b/app/pages/charts.py new file mode 100644 index 00000000..89369db9 --- /dev/null +++ b/app/pages/charts.py @@ -0,0 +1,72 @@ +import reflex as rx + +from app.templates.layout import layout_decorator +from app.www.library.charts.area.v5 import areachart_v5 +from app.www.library.charts.area.v9 import areachart_v9 +from app.www.library.charts.bar.v1 import barchart_v1 +from app.www.library.charts.bar.v5 import barchart_v5 +from app.www.library.charts.bar.v9 import barchart_v9 +from app.www.library.charts.doughnut.v1 import doughnutchart_v1 +from app.www.library.charts.line.v5 import linechart_v5 +from app.www.library.charts.line.v7 import linechart_v7 +from app.www.library.charts.line.v8 import linechart_v8 +from app.www.library.charts.pie.v1 import piechart_v1 +from app.www.library.charts.radar.v6 import radar_v6 +from app.www.library.charts.scatter.v1 import scatterchart_v1 +from components.ui.button import button + +GRID_LAYOUT = " ".join( + [ + "grid grid-cols-1 lg:grid-cols-3", + "divide-y lg:divide-y-0", + "lg:divide-x", + "divide-input/40", + "border-x border-input/40", + ] +) + + +@layout_decorator( + title="Beautiful Charts & Graphs", + description="A collection of ready-to-use chart components built with Recharts. From basic charts to rich data displays, copy and paste into your apps.", + ctas=[ + rx.el.a(button("Chart Theme", size="sm"), href="#"), + rx.el.a(button("Documentation", variant="secondary", size="sm"), href="#"), + ], +) +def chart_page(): + return rx.el.div( + areachart_v5(), + rx.el.hr(class_name="border border-input/40"), + rx.el.div( + linechart_v8(), + barchart_v1(), + doughnutchart_v1(), + class_name=GRID_LAYOUT, + ), + rx.el.hr(class_name="border border-input/40"), + barchart_v5(), + rx.el.hr(class_name="border border-input/40"), + rx.el.div( + areachart_v9(), + radar_v6(), + scatterchart_v1(), + class_name=GRID_LAYOUT, + ), + rx.el.hr(class_name="border border-input/40"), + linechart_v7(), + rx.el.hr(class_name="border border-input/40"), + rx.el.div( + linechart_v5(), + barchart_v9(), + piechart_v1(), + class_name=GRID_LAYOUT, + ), + rx.el.hr(class_name="border border-input/40"), + class_name=" ".join( + [ + "max-w-[96rem] mx-auto px-0 md:px-6", + "py-6 space-y-10", + ] + ), + ) diff --git a/app/pages/components.py b/app/pages/components.py new file mode 100644 index 00000000..15fd8e22 --- /dev/null +++ b/app/pages/components.py @@ -0,0 +1,70 @@ +from pathlib import Path + +import reflex as rx + +from app.templates.layout import layout_decorator +from components.icons.hugeicon import hi +from components.ui.button import button + +BASE_PATH = Path("components/ui") + + +def get_component_links(): + links = [] + + for file in sorted(BASE_PATH.glob("*.py")): + if file.name == "__init__.py": + continue + + name = file.stem.replace("_", " ").title() + slug = file.stem.replace("_", "-") + + links.append( + rx.el.a( + name, + href=f"/docs/components/{slug}", + class_name="py-1 hover:underline text-center", + ) + ) + + return links + + +@layout_decorator( + title="Components for Every App", + description="A collection of ready-to-use UI components for building modern applications. From simple controls to complex interface patterns, copy and paste into your apps.", + ctas=[ + rx.el.a( + button( + "Build Your Own", hi("ArrowRight02Icon", class_name="size-4"), size="sm" + ), + href="/create", + ) + ], +) +def components_page(): + return rx.el.div( + rx.el.div( + *get_component_links(), + class_name=" ".join( + [ + "grid", + "grid-cols-1", + "sm:grid-cols-2", + "md:grid-cols-3", + "lg:grid-cols-4", + "gap-6", + "max-w-6xl", + "mx-auto", + ] + ), + ), + class_name=" ".join( + [ + "max-w-[96rem]", + "mx-auto", + "px-7", + "py-6", + ] + ), + ) diff --git a/app/pages/landing.py b/app/pages/landing.py new file mode 100644 index 00000000..211d873f --- /dev/null +++ b/app/pages/landing.py @@ -0,0 +1,92 @@ +import reflex as rx + +from app.examples.components import ( + card_five, + card_four, + card_fourteen, + card_nine, + card_nineteen, + card_one, + card_seven, + card_seventeen, + card_six, + card_sixteen, + card_ten, + card_thirteen, + card_three, +) +from app.templates.layout import layout_decorator +from components.icons.hugeicon import hi +from components.ui.button import button + + +@layout_decorator( + title="The UI Library for Reflex Developers", + description="Buridan UI gives you composable, themeable components designed for Reflex. Extend, override, and ship without fighting the framework.", + ctas=[ + rx.el.a( + button( + "Build Your Own", hi("ArrowRight02Icon", class_name="size-4"), size="sm" + ), + href="/create", + ) + ], +) +def landing_page(): + + landing_desktop = rx.el.div( + card_five(), + card_four(), + card_fourteen(), + card_nine(), + card_nineteen(), + card_one(), + card_seven(), + card_seventeen(), + card_six(), + card_sixteen(), + card_ten(), + card_thirteen(), + card_three(), + class_name=" ".join( + [ + # -> main layout style + "max-w-[96rem]", + "mx-auto", + "columns-[320px]", + "px-7", + "gap-6", + "space-y-6", + # -> access inner card children to modify CSS tokens + "[&>*]:p-5", + "[&>*]:shadow-md", + "[&>*]:rounded-[2rem]", + "[&>div>div]:gap-y-4", + "[&_button]:!rounded-[2rem]", + "[&_input]:!rounded-[2rem] [&_div]:!rounded-[2rem]", + # -> create masking fade-away at the bottom of the component + "sm:mask-[linear-gradient(to_bottom,black_65%,transparent_100%)]", + "sm:mask-size-[100%_100%]", + "sm:mask-repeat-no-repeat", + ] + ), + ) + + landing_mobile = rx.el.div( + rx.color_mode_cond( + rx.el.image( + src="/landing_preview.webp", + class_name="w-[1450px] max-w-none", + ), + rx.el.image( + src="/landing_preview_dark.webp", + class_name="w-[1450px] max-w-none", + ), + ), + class_name="overflow-hidden w-full flex justify-center", + ) + + return rx.el.div( + rx.el.div(landing_mobile, class_name="flex sm:hidden w-full"), + rx.el.div(landing_desktop, class_name="hidden sm:block w-full"), + ) diff --git a/app/registry/colors.py b/app/registry/colors.py new file mode 100644 index 00000000..a3a573d5 --- /dev/null +++ b/app/registry/colors.py @@ -0,0 +1,264 @@ +# Color themes — only override primary + charts on top of a base theme. +# The seed picks a base theme first, then optionally a color theme on top. +COLOR_THEMES = [ + { + "id": "blue", + "label": "Blue", + "light": { + "primary": "oklch(0.488 0.243 264.376)", + "primary-foreground": "oklch(0.97 0.014 254.604)", + "chart-1": "oklch(0.809 0.105 251.813)", + "chart-2": "oklch(0.623 0.214 259.815)", + "chart-3": "oklch(0.546 0.245 262.881)", + "chart-4": "oklch(0.488 0.243 264.376)", + "chart-5": "oklch(0.424 0.199 265.638)", + "sidebar-primary": "oklch(0.546 0.245 262.881)", + "sidebar-primary-foreground": "oklch(0.97 0.014 254.604)", + }, + "dark": { + "primary": "oklch(0.424 0.199 265.638)", + "primary-foreground": "oklch(0.97 0.014 254.604)", + "chart-1": "oklch(0.809 0.105 251.813)", + "chart-2": "oklch(0.623 0.214 259.815)", + "chart-3": "oklch(0.546 0.245 262.881)", + "chart-4": "oklch(0.488 0.243 264.376)", + "chart-5": "oklch(0.424 0.199 265.638)", + "sidebar-primary": "oklch(0.623 0.214 259.815)", + "sidebar-primary-foreground": "oklch(0.97 0.014 254.604)", + }, + }, + { + "id": "green", + "label": "Green", + "light": { + "primary": "oklch(0.527 0.154 150.069)", + "primary-foreground": "oklch(0.982 0.018 155.826)", + "chart-1": "oklch(0.871 0.15 154.449)", + "chart-2": "oklch(0.723 0.219 149.579)", + "chart-3": "oklch(0.627 0.194 149.214)", + "chart-4": "oklch(0.527 0.154 150.069)", + "chart-5": "oklch(0.448 0.119 151.328)", + "sidebar-primary": "oklch(0.627 0.194 149.214)", + "sidebar-primary-foreground": "oklch(0.982 0.018 155.826)", + }, + "dark": { + "primary": "oklch(0.448 0.119 151.328)", + "primary-foreground": "oklch(0.982 0.018 155.826)", + "chart-1": "oklch(0.871 0.15 154.449)", + "chart-2": "oklch(0.723 0.219 149.579)", + "chart-3": "oklch(0.627 0.194 149.214)", + "chart-4": "oklch(0.527 0.154 150.069)", + "chart-5": "oklch(0.448 0.119 151.328)", + "sidebar-primary": "oklch(0.723 0.219 149.579)", + "sidebar-primary-foreground": "oklch(0.982 0.018 155.826)", + }, + }, + { + "id": "amber", + "label": "Amber", + "light": { + "primary": "oklch(0.555 0.163 48.998)", + "primary-foreground": "oklch(0.987 0.022 95.277)", + "chart-1": "oklch(0.879 0.169 91.605)", + "chart-2": "oklch(0.769 0.188 70.08)", + "chart-3": "oklch(0.666 0.179 58.318)", + "chart-4": "oklch(0.555 0.163 48.998)", + "chart-5": "oklch(0.473 0.137 46.201)", + "sidebar-primary": "oklch(0.666 0.179 58.318)", + "sidebar-primary-foreground": "oklch(0.987 0.022 95.277)", + }, + "dark": { + "primary": "oklch(0.473 0.137 46.201)", + "primary-foreground": "oklch(0.987 0.022 95.277)", + "chart-1": "oklch(0.879 0.169 91.605)", + "chart-2": "oklch(0.769 0.188 70.08)", + "chart-3": "oklch(0.666 0.179 58.318)", + "chart-4": "oklch(0.555 0.163 48.998)", + "chart-5": "oklch(0.473 0.137 46.201)", + "sidebar-primary": "oklch(0.769 0.188 70.08)", + "sidebar-primary-foreground": "oklch(0.279 0.077 45.635)", + }, + }, + { + "id": "orange", + "label": "Orange", + "light": { + "primary": "oklch(0.553 0.195 38.402)", + "primary-foreground": "oklch(0.98 0.016 73.684)", + "chart-1": "oklch(0.837 0.128 66.29)", + "chart-2": "oklch(0.705 0.213 47.604)", + "chart-3": "oklch(0.646 0.222 41.116)", + "chart-4": "oklch(0.553 0.195 38.402)", + "chart-5": "oklch(0.47 0.157 37.304)", + "sidebar-primary": "oklch(0.646 0.222 41.116)", + "sidebar-primary-foreground": "oklch(0.98 0.016 73.684)", + }, + "dark": { + "primary": "oklch(0.47 0.157 37.304)", + "primary-foreground": "oklch(0.98 0.016 73.684)", + "chart-1": "oklch(0.837 0.128 66.29)", + "chart-2": "oklch(0.705 0.213 47.604)", + "chart-3": "oklch(0.646 0.222 41.116)", + "chart-4": "oklch(0.553 0.195 38.402)", + "chart-5": "oklch(0.47 0.157 37.304)", + "sidebar-primary": "oklch(0.705 0.213 47.604)", + "sidebar-primary-foreground": "oklch(0.98 0.016 73.684)", + }, + }, + { + "id": "fuchsia", + "label": "Fuchsia", + "light": { + "primary": "oklch(0.518 0.253 323.949)", + "primary-foreground": "oklch(0.977 0.017 320.058)", + "chart-1": "oklch(0.833 0.145 321.434)", + "chart-2": "oklch(0.667 0.295 322.15)", + "chart-3": "oklch(0.591 0.293 322.896)", + "chart-4": "oklch(0.518 0.253 323.949)", + "chart-5": "oklch(0.452 0.211 324.591)", + "sidebar-primary": "oklch(0.591 0.293 322.896)", + "sidebar-primary-foreground": "oklch(0.977 0.017 320.058)", + }, + "dark": { + "primary": "oklch(0.452 0.211 324.591)", + "primary-foreground": "oklch(0.977 0.017 320.058)", + "chart-1": "oklch(0.833 0.145 321.434)", + "chart-2": "oklch(0.667 0.295 322.15)", + "chart-3": "oklch(0.591 0.293 322.896)", + "chart-4": "oklch(0.518 0.253 323.949)", + "chart-5": "oklch(0.452 0.211 324.591)", + "sidebar-primary": "oklch(0.667 0.295 322.15)", + "sidebar-primary-foreground": "oklch(0.977 0.017 320.058)", + }, + }, + { + "id": "indigo", + "label": "Indigo", + "light": { + "primary": "oklch(0.457 0.24 277.023)", + "primary-foreground": "oklch(0.962 0.018 272.314)", + "chart-1": "oklch(0.785 0.115 274.713)", + "chart-2": "oklch(0.585 0.233 277.117)", + "chart-3": "oklch(0.511 0.262 276.966)", + "chart-4": "oklch(0.457 0.24 277.023)", + "chart-5": "oklch(0.398 0.195 277.366)", + "sidebar-primary": "oklch(0.511 0.262 276.966)", + "sidebar-primary-foreground": "oklch(0.962 0.018 272.314)", + }, + "dark": { + "primary": "oklch(0.398 0.195 277.366)", + "primary-foreground": "oklch(0.962 0.018 272.314)", + "chart-1": "oklch(0.785 0.115 274.713)", + "chart-2": "oklch(0.585 0.233 277.117)", + "chart-3": "oklch(0.511 0.262 276.966)", + "chart-4": "oklch(0.457 0.24 277.023)", + "chart-5": "oklch(0.398 0.195 277.366)", + "sidebar-primary": "oklch(0.585 0.233 277.117)", + "sidebar-primary-foreground": "oklch(0.962 0.018 272.314)", + }, + }, + { + "id": "pink", + "label": "Pink", + "light": { + "primary": "oklch(0.525 0.223 3.958)", + "primary-foreground": "oklch(0.971 0.014 343.198)", + "chart-1": "oklch(0.823 0.12 346.018)", + "chart-2": "oklch(0.656 0.241 354.308)", + "chart-3": "oklch(0.592 0.249 0.584)", + "chart-4": "oklch(0.525 0.223 3.958)", + "chart-5": "oklch(0.459 0.187 3.815)", + "sidebar-primary": "oklch(0.592 0.249 0.584)", + "sidebar-primary-foreground": "oklch(0.971 0.014 343.198)", + }, + "dark": { + "primary": "oklch(0.459 0.187 3.815)", + "primary-foreground": "oklch(0.971 0.014 343.198)", + "chart-1": "oklch(0.823 0.12 346.018)", + "chart-2": "oklch(0.656 0.241 354.308)", + "chart-3": "oklch(0.592 0.249 0.584)", + "chart-4": "oklch(0.525 0.223 3.958)", + "chart-5": "oklch(0.459 0.187 3.815)", + "sidebar-primary": "oklch(0.656 0.241 354.308)", + "sidebar-primary-foreground": "oklch(0.971 0.014 343.198)", + }, + }, + { + "id": "emerald", + "label": "Emerald", + "light": { + "primary": "oklch(0.508 0.118 165.612)", + "primary-foreground": "oklch(0.979 0.021 166.113)", + "chart-1": "oklch(0.845 0.143 164.978)", + "chart-2": "oklch(0.696 0.17 162.48)", + "chart-3": "oklch(0.596 0.145 163.225)", + "chart-4": "oklch(0.508 0.118 165.612)", + "chart-5": "oklch(0.432 0.095 166.913)", + "sidebar-primary": "oklch(0.596 0.145 163.225)", + "sidebar-primary-foreground": "oklch(0.979 0.021 166.113)", + }, + "dark": { + "primary": "oklch(0.432 0.095 166.913)", + "primary-foreground": "oklch(0.979 0.021 166.113)", + "chart-1": "oklch(0.845 0.143 164.978)", + "chart-2": "oklch(0.696 0.17 162.48)", + "chart-3": "oklch(0.596 0.145 163.225)", + "chart-4": "oklch(0.508 0.118 165.612)", + "chart-5": "oklch(0.432 0.095 166.913)", + "sidebar-primary": "oklch(0.696 0.17 162.48)", + "sidebar-primary-foreground": "oklch(0.979 0.021 166.113)", + }, + }, + { + "id": "cyan", + "label": "Cyan", + "light": { + "primary": "oklch(0.52 0.105 223.128)", + "primary-foreground": "oklch(0.984 0.019 200.873)", + "chart-1": "oklch(0.865 0.127 207.078)", + "chart-2": "oklch(0.715 0.143 215.221)", + "chart-3": "oklch(0.609 0.126 221.723)", + "chart-4": "oklch(0.52 0.105 223.128)", + "chart-5": "oklch(0.45 0.085 224.283)", + "sidebar-primary": "oklch(0.609 0.126 221.723)", + "sidebar-primary-foreground": "oklch(0.984 0.019 200.873)", + }, + "dark": { + "primary": "oklch(0.45 0.085 224.283)", + "primary-foreground": "oklch(0.984 0.019 200.873)", + "chart-1": "oklch(0.865 0.127 207.078)", + "chart-2": "oklch(0.715 0.143 215.221)", + "chart-3": "oklch(0.609 0.126 221.723)", + "chart-4": "oklch(0.52 0.105 223.128)", + "chart-5": "oklch(0.45 0.085 224.283)", + "sidebar-primary": "oklch(0.715 0.143 215.221)", + "sidebar-primary-foreground": "oklch(0.302 0.056 229.695)", + }, + }, + { + "id": "lime", + "label": "Lime", + "light": { + "primary": "oklch(0.841 0.238 128.85)", + "primary-foreground": "oklch(0.405 0.101 131.063)", + "chart-1": "oklch(0.897 0.196 126.665)", + "chart-2": "oklch(0.768 0.233 130.85)", + "chart-3": "oklch(0.648 0.2 131.684)", + "chart-4": "oklch(0.532 0.157 131.589)", + "chart-5": "oklch(0.453 0.124 130.933)", + "sidebar-primary": "oklch(0.648 0.2 131.684)", + "sidebar-primary-foreground": "oklch(0.986 0.031 120.757)", + }, + "dark": { + "primary": "oklch(0.768 0.233 130.85)", + "primary-foreground": "oklch(0.405 0.101 131.063)", + "chart-1": "oklch(0.897 0.196 126.665)", + "chart-2": "oklch(0.768 0.233 130.85)", + "chart-3": "oklch(0.648 0.2 131.684)", + "chart-4": "oklch(0.532 0.157 131.589)", + "chart-5": "oklch(0.453 0.124 130.933)", + "sidebar-primary": "oklch(0.768 0.233 130.85)", + "sidebar-primary-foreground": "oklch(0.274 0.072 132.109)", + }, + }, +] diff --git a/app/registry/components.py b/app/registry/components.py new file mode 100644 index 00000000..77aec57e --- /dev/null +++ b/app/registry/components.py @@ -0,0 +1,159 @@ +"""Buridan UI Component Registry.""" + +COMPONENT_REGISTRY = { + # --- Utilities --- + "twmerge": { + "files": ["components/utils/twmerge.py"], + "dependencies": [], + }, + "component": { + "files": ["components/ui/component.py"], + "dependencies": ["twmerge"], + }, + "base_ui": { + "files": ["components/ui/base_ui.py"], + "dependencies": ["component"], + }, + + # --- Icons --- + "hugeicon": { + "files": ["components/icons/hugeicon.py"], + "dependencies": ["component"], + }, + "others_icons": { + "files": ["components/icons/others.py"], + "dependencies": ["twmerge"], + }, + + # --- Charts --- + "charts": { + "files": ["components/charts/chart_tooltip.py"], + "dependencies": [], + }, + "chart_tooltip": { + "files": ["components/charts/chart_tooltip.py"], + "dependencies": [], + }, + + # --- UI Components --- + "accordion": { + "files": ["components/ui/accordion.py"], + "dependencies": ["button", "hugeicon", "base_ui"], + }, + "avatar": { + "files": ["components/ui/avatar.py"], + "dependencies": ["base_ui"], + }, + "badge": { + "files": ["components/ui/badge.py"], + "dependencies": ["component"], + }, + "breadcrumb": { + "files": ["components/ui/breadcrumb.py"], + "dependencies": [], + }, + "button": { + "files": ["components/ui/button.py"], + "dependencies": ["others_icons", "component"], + }, + "card": { + "files": ["components/ui/card.py"], + "dependencies": ["component"], + }, + "checkbox": { + "files": ["components/ui/checkbox.py"], + "dependencies": ["hugeicon", "twmerge", "base_ui"], + }, + "collapsible": { + "files": ["components/ui/collapsible.py"], + "dependencies": ["base_ui"], + }, + "context_menu": { + "files": ["components/ui/context_menu.py"], + "dependencies": ["twmerge", "base_ui", "button"], + }, + "dialog": { + "files": ["components/ui/dialog.py"], + "dependencies": ["hugeicon", "base_ui", "button"], + }, + "field": { + "files": ["components/ui/field.py"], + "dependencies": ["base_ui"], + }, + "input": { + "files": ["components/ui/input.py"], + "dependencies": [], + }, + "input_group": { + "files": ["components/ui/input_group.py"], + "dependencies": [], + }, + "kbd": { + "files": ["components/ui/kbd.py"], + "dependencies": [], + }, + "link": { + "files": ["components/ui/link.py"], + "dependencies": ["hugeicon", "twmerge"], + }, + "menu": { + "files": ["components/ui/menu.py"], + "dependencies": ["hugeicon", "others_icons", "twmerge", "base_ui", "button"], + }, + "metric": { + "files": ["components/ui/metric.py"], + "dependencies": ["twmerge", "component"], + }, + "popover": { + "files": ["components/ui/popover.py"], + "dependencies": ["twmerge", "base_ui"], + }, + "scroll_area": { + "files": ["components/ui/scroll_area.py"], + "dependencies": ["twmerge", "base_ui"], + }, + "select": { + "files": ["components/ui/select.py"], + "dependencies": ["hugeicon", "others_icons", "twmerge", "base_ui", "button"], + }, + "skeleton": { + "files": ["components/ui/skeleton.py"], + "dependencies": ["twmerge"], + }, + "slider": { + "files": ["components/ui/slider.py"], + "dependencies": ["base_ui"], + }, + "switch": { + "files": ["components/ui/switch.py"], + "dependencies": ["base_ui"], + }, + "table": { + "files": ["components/ui/table.py"], + "dependencies": ["twmerge", "component"], + }, + "tabs": { + "files": ["components/ui/tabs.py"], + "dependencies": ["base_ui"], + }, + "textarea": { + "files": ["components/ui/textarea.py"], + "dependencies": ["component"], + }, + "toggle": { + "files": ["components/ui/toggle.py"], + "dependencies": ["twmerge", "base_ui"], + }, + "toggle_group": { + "files": ["components/ui/toggle_group.py"], + "dependencies": ["base_ui"], + }, + "tooltip": { + "files": ["components/ui/tooltip.py"], + "dependencies": ["others_icons", "base_ui"], + }, + "typography": { + "files": ["components/ui/typography.py"], + "dependencies": [], + }, +} diff --git a/app/registry/fonts.py b/app/registry/fonts.py new file mode 100644 index 00000000..7a858eae --- /dev/null +++ b/app/registry/fonts.py @@ -0,0 +1,95 @@ +FONT_REGISTRY = [ + # --------------------------------------------------------------------------- + # SANS-SERIF FONTS (Ideal for Clean UI, Dashboards, and SaaS apps) + # --------------------------------------------------------------------------- + { + "id": "inter", + "label": "Inter (Sans)", + "category": "sans", + "vars": {"--font-family": '"Inter", sans-serif'}, + }, + { + "id": "geist", + "label": "Geist Sans (Sans)", + "category": "sans", + "vars": {"--font-family": '"Geist Sans", sans-serif'}, + }, + { + "id": "roboto", + "label": "Roboto (Sans)", + "category": "sans", + "vars": {"--font-family": '"Roboto", sans-serif'}, + }, + { + "id": "outfit", + "label": "Outfit (Geometric)", + "category": "sans", + "vars": {"--font-family": '"Outfit", sans-serif'}, + }, + { + "id": "plus-jakarta", + "label": "Plus Jakarta Sans", + "category": "sans", + "vars": {"--font-family": '"Plus Jakarta Sans", sans-serif'}, + }, + { + "id": "public-sans", + "label": "Public Sans (Gov/Clean)", + "category": "sans", + "vars": {"--font-family": '"Public Sans", sans-serif'}, + }, + # --------------------------------------------------------------------------- + # SERIF FONTS (Ideal for Editorial blocks, Blogs, and Elegant themes) + # --------------------------------------------------------------------------- + { + "id": "playfair", + "label": "Playfair Display", + "category": "serif", + "vars": {"--font-family": '"Playfair Display", serif'}, + }, + { + "id": "merriweather", + "label": "Merriweather", + "category": "serif", + "vars": {"--font-family": '"Merriweather", serif'}, + }, + { + "id": "lora", + "label": "Lora (Modern Book)", + "category": "serif", + "vars": {"--font-family": '"Lora", serif'}, + }, + { + "id": "instrument-serif", + "label": "Instrument Serif", + "category": "serif", + "vars": {"--font-family": '"Instrument Serif", serif'}, + }, + # --------------------------------------------------------------------------- + # MONOSPACE FONTS (Ideal for Forge layouts, DevTools, Terminals) + # --------------------------------------------------------------------------- + { + "id": "fira-code", + "label": "Fira Code", + "category": "mono", + "vars": {"--font-family": '"Fira Code", monospace'}, + }, + { + "id": "jetbrains-mono", + "label": "JetBrains Mono", + "category": "mono", + "vars": {"--font-family": '"JetBrains Mono", monospace'}, + }, + { + "id": "geist-mono", + "label": "Geist Mono", + "category": "mono", + "vars": {"--font-family": '"Geist Mono", monospace'}, + }, + { + "id": "ibm-plex-mono", + "label": "IBM Plex Mono", + "category": "mono", + "vars": {"--font-family": '"IBM Plex Mono", monospace'}, + }, +] diff --git a/app/registry/radii.py b/app/registry/radii.py new file mode 100644 index 00000000..d25db06c --- /dev/null +++ b/app/registry/radii.py @@ -0,0 +1,6 @@ +RADIUS_OPTIONS = [ + ("None", "0rem"), + ("Small", "0.45rem"), + ("Medium", "0.625rem"), + ("Large", "0.875rem"), +] diff --git a/app/registry/styles.py b/app/registry/styles.py new file mode 100644 index 00000000..9a9abb94 --- /dev/null +++ b/app/registry/styles.py @@ -0,0 +1,66 @@ +# --------------------------------------------------------------------------- +# STYLE REGISTRY +# Add new entries here to expose new style personalities. +# --------------------------------------------------------------------------- +STYLE_REGISTRY = [ + { + "id": "vega", + "label": "Vega", + "description": "Balanced & approachable", + "vars": { + "--radius": "0.5rem", + "--shadow": "0 1px 3px rgba(0,0,0,0.08)", + "--border-width": "1px", + "--card-padding": "1.25rem", + "--card-gap": "1rem", + }, + }, + { + "id": "luma", + "label": "Luma", + "description": "Soft & rounded", + "vars": { + "--radius": "1.25rem", + "--shadow": "0 8px 24px rgba(0,0,0,0.12)", + "--border-width": "1px", + "--card-padding": "1.5rem", + "--card-gap": "1.5rem", + }, + }, + { + "id": "nova", + "label": "Nova", + "description": "Sharp & precise", + "vars": { + "--radius": "0rem", + "--shadow": "none", + "--border-width": "1.5px", + "--card-padding": "1.25rem", + "--card-gap": "0.75rem", + }, + }, + { + "id": "drift", + "label": "Drift", + "description": "Airy & minimal", + "vars": { + "--radius": "0.75rem", + "--shadow": "0 2px 8px rgba(0,0,0,0.06)", + "--border-width": "0.5px", + "--card-padding": "1.5rem", + "--card-gap": "2rem", + }, + }, + { + "id": "forge", + "label": "Forge", + "description": "Dense & utilitarian", + "vars": { + "--radius": "0.25rem", + "--shadow": "2px 2px 0px rgba(0,0,0,0.15)", + "--border-width": "2px", + "--card-padding": "1.25rem", + "--card-gap": "1.25rem", + }, + }, +] diff --git a/app/registry/themes.py b/app/registry/themes.py new file mode 100644 index 00000000..ce14fcbf --- /dev/null +++ b/app/registry/themes.py @@ -0,0 +1,433 @@ +# --------------------------------------------------------------------------- +# SHADCN COLOR THEMES +# Sourced from https://ui.shadcn.com/themes +# Two tiers: +# "base" themes have full light+dark palettes (neutral, stone, zinc, etc.) +# "color" themes only define primary+charts and inherit from a base theme +# +# To add a new theme: append an entry. The seed engine picks it automatically. +# --------------------------------------------------------------------------- + +# Base themes — full light + dark palette +BASE_THEMES = [ + { + "id": "neutral", + "label": "Neutral", + "light": { + "background": "oklch(1 0 0)", + "foreground": "oklch(0.145 0 0)", + "card": "oklch(1 0 0)", + "card-foreground": "oklch(0.145 0 0)", + "popover": "oklch(1 0 0)", + "popover-foreground": "oklch(0.145 0 0)", + "primary": "oklch(0.205 0 0)", + "primary-foreground": "oklch(0.985 0 0)", + "secondary": "oklch(0.97 0 0)", + "secondary-foreground": "oklch(0.205 0 0)", + "muted": "oklch(0.97 0 0)", + "muted-foreground": "oklch(0.556 0 0)", + "accent": "oklch(0.97 0 0)", + "accent-foreground": "oklch(0.205 0 0)", + "destructive": "oklch(0.577 0.245 27.325)", + "border": "oklch(0.922 0 0)", + "input": "oklch(0.922 0 0)", + "ring": "oklch(0.708 0 0)", + "chart-1": "oklch(0.87 0 0)", + "chart-2": "oklch(0.556 0 0)", + "chart-3": "oklch(0.439 0 0)", + "chart-4": "oklch(0.371 0 0)", + "chart-5": "oklch(0.269 0 0)", + "sidebar": "oklch(0.985 0 0)", + "sidebar-foreground": "oklch(0.145 0 0)", + "sidebar-primary": "oklch(0.205 0 0)", + "sidebar-primary-foreground": "oklch(0.985 0 0)", + "sidebar-accent": "oklch(0.97 0 0)", + "sidebar-accent-foreground": "oklch(0.205 0 0)", + "sidebar-border": "oklch(0.922 0 0)", + "sidebar-ring": "oklch(0.708 0 0)", + }, + "dark": { + "background": "oklch(0.145 0 0)", + "foreground": "oklch(0.985 0 0)", + "card": "oklch(0.205 0 0)", + "card-foreground": "oklch(0.985 0 0)", + "popover": "oklch(0.205 0 0)", + "popover-foreground": "oklch(0.985 0 0)", + "primary": "oklch(0.922 0 0)", + "primary-foreground": "oklch(0.205 0 0)", + "secondary": "oklch(0.269 0 0)", + "secondary-foreground": "oklch(0.985 0 0)", + "muted": "oklch(0.269 0 0)", + "muted-foreground": "oklch(0.708 0 0)", + "accent": "oklch(0.269 0 0)", + "accent-foreground": "oklch(0.985 0 0)", + "destructive": "oklch(0.704 0.191 22.216)", + "border": "oklch(1 0 0 / 10%)", + "input": "oklch(1 0 0 / 15%)", + "ring": "oklch(0.556 0 0)", + "chart-1": "oklch(0.87 0 0)", + "chart-2": "oklch(0.556 0 0)", + "chart-3": "oklch(0.439 0 0)", + "chart-4": "oklch(0.371 0 0)", + "chart-5": "oklch(0.269 0 0)", + "sidebar": "oklch(0.205 0 0)", + "sidebar-foreground": "oklch(0.985 0 0)", + "sidebar-primary": "oklch(0.488 0.243 264.376)", + "sidebar-primary-foreground": "oklch(0.985 0 0)", + "sidebar-accent": "oklch(0.269 0 0)", + "sidebar-accent-foreground": "oklch(0.985 0 0)", + "sidebar-border": "oklch(1 0 0 / 10%)", + "sidebar-ring": "oklch(0.556 0 0)", + }, + }, + { + "id": "stone", + "label": "Stone", + "light": { + "background": "oklch(1 0 0)", + "foreground": "oklch(0.147 0.004 49.25)", + "card": "oklch(1 0 0)", + "card-foreground": "oklch(0.147 0.004 49.25)", + "popover": "oklch(1 0 0)", + "popover-foreground": "oklch(0.147 0.004 49.25)", + "primary": "oklch(0.216 0.006 56.043)", + "primary-foreground": "oklch(0.985 0.001 106.423)", + "secondary": "oklch(0.97 0.001 106.424)", + "secondary-foreground": "oklch(0.216 0.006 56.043)", + "muted": "oklch(0.97 0.001 106.424)", + "muted-foreground": "oklch(0.553 0.013 58.071)", + "accent": "oklch(0.97 0.001 106.424)", + "accent-foreground": "oklch(0.216 0.006 56.043)", + "destructive": "oklch(0.577 0.245 27.325)", + "border": "oklch(0.923 0.003 48.717)", + "input": "oklch(0.923 0.003 48.717)", + "ring": "oklch(0.709 0.01 56.259)", + "chart-1": "oklch(0.869 0.005 56.366)", + "chart-2": "oklch(0.553 0.013 58.071)", + "chart-3": "oklch(0.444 0.011 73.639)", + "chart-4": "oklch(0.374 0.01 67.558)", + "chart-5": "oklch(0.268 0.007 34.298)", + "sidebar": "oklch(0.985 0.001 106.423)", + "sidebar-foreground": "oklch(0.147 0.004 49.25)", + "sidebar-primary": "oklch(0.216 0.006 56.043)", + "sidebar-primary-foreground": "oklch(0.985 0.001 106.423)", + "sidebar-accent": "oklch(0.97 0.001 106.424)", + "sidebar-accent-foreground": "oklch(0.216 0.006 56.043)", + "sidebar-border": "oklch(0.923 0.003 48.717)", + "sidebar-ring": "oklch(0.709 0.01 56.259)", + }, + "dark": { + "background": "oklch(0.147 0.004 49.25)", + "foreground": "oklch(0.985 0.001 106.423)", + "card": "oklch(0.216 0.006 56.043)", + "card-foreground": "oklch(0.985 0.001 106.423)", + "popover": "oklch(0.216 0.006 56.043)", + "popover-foreground": "oklch(0.985 0.001 106.423)", + "primary": "oklch(0.923 0.003 48.717)", + "primary-foreground": "oklch(0.216 0.006 56.043)", + "secondary": "oklch(0.268 0.007 34.298)", + "secondary-foreground": "oklch(0.985 0.001 106.423)", + "muted": "oklch(0.268 0.007 34.298)", + "muted-foreground": "oklch(0.709 0.01 56.259)", + "accent": "oklch(0.268 0.007 34.298)", + "accent-foreground": "oklch(0.985 0.001 106.423)", + "destructive": "oklch(0.704 0.191 22.216)", + "border": "oklch(1 0 0 / 10%)", + "input": "oklch(1 0 0 / 15%)", + "ring": "oklch(0.553 0.013 58.071)", + "chart-1": "oklch(0.869 0.005 56.366)", + "chart-2": "oklch(0.553 0.013 58.071)", + "chart-3": "oklch(0.444 0.011 73.639)", + "chart-4": "oklch(0.374 0.01 67.558)", + "chart-5": "oklch(0.268 0.007 34.298)", + "sidebar": "oklch(0.216 0.006 56.043)", + "sidebar-foreground": "oklch(0.985 0.001 106.423)", + "sidebar-primary": "oklch(0.488 0.243 264.376)", + "sidebar-primary-foreground": "oklch(0.985 0.001 106.423)", + "sidebar-accent": "oklch(0.268 0.007 34.298)", + "sidebar-accent-foreground": "oklch(0.985 0.001 106.423)", + "sidebar-border": "oklch(1 0 0 / 10%)", + "sidebar-ring": "oklch(0.553 0.013 58.071)", + }, + }, + { + "id": "zinc", + "label": "Zinc", + "light": { + "background": "oklch(1 0 0)", + "foreground": "oklch(0.141 0.005 285.823)", + "card": "oklch(1 0 0)", + "card-foreground": "oklch(0.141 0.005 285.823)", + "popover": "oklch(1 0 0)", + "popover-foreground": "oklch(0.141 0.005 285.823)", + "primary": "oklch(0.21 0.006 285.885)", + "primary-foreground": "oklch(0.985 0 0)", + "secondary": "oklch(0.967 0.001 286.375)", + "secondary-foreground": "oklch(0.21 0.006 285.885)", + "muted": "oklch(0.967 0.001 286.375)", + "muted-foreground": "oklch(0.552 0.016 285.938)", + "accent": "oklch(0.967 0.001 286.375)", + "accent-foreground": "oklch(0.21 0.006 285.885)", + "destructive": "oklch(0.577 0.245 27.325)", + "border": "oklch(0.92 0.004 286.32)", + "input": "oklch(0.92 0.004 286.32)", + "ring": "oklch(0.705 0.015 286.067)", + "chart-1": "oklch(0.871 0.006 286.286)", + "chart-2": "oklch(0.552 0.016 285.938)", + "chart-3": "oklch(0.442 0.017 285.786)", + "chart-4": "oklch(0.37 0.013 285.805)", + "chart-5": "oklch(0.274 0.006 286.033)", + "sidebar": "oklch(0.985 0 0)", + "sidebar-foreground": "oklch(0.141 0.005 285.823)", + "sidebar-primary": "oklch(0.21 0.006 285.885)", + "sidebar-primary-foreground": "oklch(0.985 0 0)", + "sidebar-accent": "oklch(0.967 0.001 286.375)", + "sidebar-accent-foreground": "oklch(0.21 0.006 285.885)", + "sidebar-border": "oklch(0.92 0.004 286.32)", + "sidebar-ring": "oklch(0.705 0.015 286.067)", + }, + "dark": { + "background": "oklch(0.141 0.005 285.823)", + "foreground": "oklch(0.985 0 0)", + "card": "oklch(0.21 0.006 285.885)", + "card-foreground": "oklch(0.985 0 0)", + "popover": "oklch(0.21 0.006 285.885)", + "popover-foreground": "oklch(0.985 0 0)", + "primary": "oklch(0.92 0.004 286.32)", + "primary-foreground": "oklch(0.21 0.006 285.885)", + "secondary": "oklch(0.274 0.006 286.033)", + "secondary-foreground": "oklch(0.985 0 0)", + "muted": "oklch(0.274 0.006 286.033)", + "muted-foreground": "oklch(0.705 0.015 286.067)", + "accent": "oklch(0.274 0.006 286.033)", + "accent-foreground": "oklch(0.985 0 0)", + "destructive": "oklch(0.704 0.191 22.216)", + "border": "oklch(1 0 0 / 10%)", + "input": "oklch(1 0 0 / 15%)", + "ring": "oklch(0.552 0.016 285.938)", + "chart-1": "oklch(0.871 0.006 286.286)", + "chart-2": "oklch(0.552 0.016 285.938)", + "chart-3": "oklch(0.442 0.017 285.786)", + "chart-4": "oklch(0.37 0.013 285.805)", + "chart-5": "oklch(0.274 0.006 286.033)", + "sidebar": "oklch(0.21 0.006 285.885)", + "sidebar-foreground": "oklch(0.985 0 0)", + "sidebar-primary": "oklch(0.488 0.243 264.376)", + "sidebar-primary-foreground": "oklch(0.985 0 0)", + "sidebar-accent": "oklch(0.274 0.006 286.033)", + "sidebar-accent-foreground": "oklch(0.985 0 0)", + "sidebar-border": "oklch(1 0 0 / 10%)", + "sidebar-ring": "oklch(0.552 0.016 285.938)", + }, + }, + { + "id": "mauve", + "label": "Mauve", + "light": { + "background": "oklch(1 0 0)", + "foreground": "oklch(0.145 0.008 326)", + "card": "oklch(1 0 0)", + "card-foreground": "oklch(0.145 0.008 326)", + "popover": "oklch(1 0 0)", + "popover-foreground": "oklch(0.145 0.008 326)", + "primary": "oklch(0.212 0.019 322.12)", + "primary-foreground": "oklch(0.985 0 0)", + "secondary": "oklch(0.96 0.003 325.6)", + "secondary-foreground": "oklch(0.212 0.019 322.12)", + "muted": "oklch(0.96 0.003 325.6)", + "muted-foreground": "oklch(0.542 0.034 322.5)", + "accent": "oklch(0.96 0.003 325.6)", + "accent-foreground": "oklch(0.212 0.019 322.12)", + "destructive": "oklch(0.577 0.245 27.325)", + "border": "oklch(0.922 0.005 325.62)", + "input": "oklch(0.922 0.005 325.62)", + "ring": "oklch(0.711 0.019 323.02)", + "chart-1": "oklch(0.865 0.012 325.68)", + "chart-2": "oklch(0.542 0.034 322.5)", + "chart-3": "oklch(0.435 0.029 321.78)", + "chart-4": "oklch(0.364 0.029 323.89)", + "chart-5": "oklch(0.263 0.024 320.12)", + "sidebar": "oklch(0.985 0 0)", + "sidebar-foreground": "oklch(0.145 0.008 326)", + "sidebar-primary": "oklch(0.212 0.019 322.12)", + "sidebar-primary-foreground": "oklch(0.985 0 0)", + "sidebar-accent": "oklch(0.96 0.003 325.6)", + "sidebar-accent-foreground": "oklch(0.212 0.019 322.12)", + "sidebar-border": "oklch(0.922 0.005 325.62)", + "sidebar-ring": "oklch(0.711 0.019 323.02)", + }, + "dark": { + "background": "oklch(0.145 0.008 326)", + "foreground": "oklch(0.985 0 0)", + "card": "oklch(0.212 0.019 322.12)", + "card-foreground": "oklch(0.985 0 0)", + "popover": "oklch(0.212 0.019 322.12)", + "popover-foreground": "oklch(0.985 0 0)", + "primary": "oklch(0.922 0.005 325.62)", + "primary-foreground": "oklch(0.212 0.019 322.12)", + "secondary": "oklch(0.263 0.024 320.12)", + "secondary-foreground": "oklch(0.985 0 0)", + "muted": "oklch(0.263 0.024 320.12)", + "muted-foreground": "oklch(0.711 0.019 323.02)", + "accent": "oklch(0.263 0.024 320.12)", + "accent-foreground": "oklch(0.985 0 0)", + "destructive": "oklch(0.704 0.191 22.216)", + "border": "oklch(1 0 0 / 10%)", + "input": "oklch(1 0 0 / 15%)", + "ring": "oklch(0.542 0.034 322.5)", + "chart-1": "oklch(0.865 0.012 325.68)", + "chart-2": "oklch(0.542 0.034 322.5)", + "chart-3": "oklch(0.435 0.029 321.78)", + "chart-4": "oklch(0.364 0.029 323.89)", + "chart-5": "oklch(0.263 0.024 320.12)", + "sidebar": "oklch(0.212 0.019 322.12)", + "sidebar-foreground": "oklch(0.985 0 0)", + "sidebar-primary": "oklch(0.488 0.243 264.376)", + "sidebar-primary-foreground": "oklch(0.985 0 0)", + "sidebar-accent": "oklch(0.263 0.024 320.12)", + "sidebar-accent-foreground": "oklch(0.985 0 0)", + "sidebar-border": "oklch(1 0 0 / 10%)", + "sidebar-ring": "oklch(0.542 0.034 322.5)", + }, + }, + { + "id": "olive", + "label": "Olive", + "light": { + "background": "oklch(1 0 0)", + "foreground": "oklch(0.153 0.006 107.1)", + "card": "oklch(1 0 0)", + "card-foreground": "oklch(0.153 0.006 107.1)", + "popover": "oklch(1 0 0)", + "popover-foreground": "oklch(0.153 0.006 107.1)", + "primary": "oklch(0.228 0.013 107.4)", + "primary-foreground": "oklch(0.988 0.003 106.5)", + "secondary": "oklch(0.966 0.005 106.5)", + "secondary-foreground": "oklch(0.228 0.013 107.4)", + "muted": "oklch(0.966 0.005 106.5)", + "muted-foreground": "oklch(0.58 0.031 107.3)", + "accent": "oklch(0.966 0.005 106.5)", + "accent-foreground": "oklch(0.228 0.013 107.4)", + "destructive": "oklch(0.577 0.245 27.325)", + "border": "oklch(0.93 0.007 106.5)", + "input": "oklch(0.93 0.007 106.5)", + "ring": "oklch(0.737 0.021 106.9)", + "chart-1": "oklch(0.88 0.011 106.6)", + "chart-2": "oklch(0.58 0.031 107.3)", + "chart-3": "oklch(0.466 0.025 107.3)", + "chart-4": "oklch(0.394 0.023 107.4)", + "chart-5": "oklch(0.286 0.016 107.4)", + "sidebar": "oklch(0.988 0.003 106.5)", + "sidebar-foreground": "oklch(0.153 0.006 107.1)", + "sidebar-primary": "oklch(0.228 0.013 107.4)", + "sidebar-primary-foreground": "oklch(0.988 0.003 106.5)", + "sidebar-accent": "oklch(0.966 0.005 106.5)", + "sidebar-accent-foreground": "oklch(0.228 0.013 107.4)", + "sidebar-border": "oklch(0.93 0.007 106.5)", + "sidebar-ring": "oklch(0.737 0.021 106.9)", + }, + "dark": { + "background": "oklch(0.153 0.006 107.1)", + "foreground": "oklch(0.988 0.003 106.5)", + "card": "oklch(0.228 0.013 107.4)", + "card-foreground": "oklch(0.988 0.003 106.5)", + "popover": "oklch(0.228 0.013 107.4)", + "popover-foreground": "oklch(0.988 0.003 106.5)", + "primary": "oklch(0.93 0.007 106.5)", + "primary-foreground": "oklch(0.228 0.013 107.4)", + "secondary": "oklch(0.286 0.016 107.4)", + "secondary-foreground": "oklch(0.988 0.003 106.5)", + "muted": "oklch(0.286 0.016 107.4)", + "muted-foreground": "oklch(0.737 0.021 106.9)", + "accent": "oklch(0.286 0.016 107.4)", + "accent-foreground": "oklch(0.988 0.003 106.5)", + "destructive": "oklch(0.704 0.191 22.216)", + "border": "oklch(1 0 0 / 10%)", + "input": "oklch(1 0 0 / 15%)", + "ring": "oklch(0.58 0.031 107.3)", + "chart-1": "oklch(0.88 0.011 106.6)", + "chart-2": "oklch(0.58 0.031 107.3)", + "chart-3": "oklch(0.466 0.025 107.3)", + "chart-4": "oklch(0.394 0.023 107.4)", + "chart-5": "oklch(0.286 0.016 107.4)", + "sidebar": "oklch(0.228 0.013 107.4)", + "sidebar-foreground": "oklch(0.988 0.003 106.5)", + "sidebar-primary": "oklch(0.488 0.243 264.376)", + "sidebar-primary-foreground": "oklch(0.988 0.003 106.5)", + "sidebar-accent": "oklch(0.286 0.016 107.4)", + "sidebar-accent-foreground": "oklch(0.988 0.003 106.5)", + "sidebar-border": "oklch(1 0 0 / 10%)", + "sidebar-ring": "oklch(0.58 0.031 107.3)", + }, + }, + { + "id": "mist", + "label": "Mist", + "light": { + "background": "oklch(1 0 0)", + "foreground": "oklch(0.148 0.004 228.8)", + "card": "oklch(1 0 0)", + "card-foreground": "oklch(0.148 0.004 228.8)", + "popover": "oklch(1 0 0)", + "popover-foreground": "oklch(0.148 0.004 228.8)", + "primary": "oklch(0.218 0.008 223.9)", + "primary-foreground": "oklch(0.987 0.002 197.1)", + "secondary": "oklch(0.963 0.002 197.1)", + "secondary-foreground": "oklch(0.218 0.008 223.9)", + "muted": "oklch(0.963 0.002 197.1)", + "muted-foreground": "oklch(0.56 0.021 213.5)", + "accent": "oklch(0.963 0.002 197.1)", + "accent-foreground": "oklch(0.218 0.008 223.9)", + "destructive": "oklch(0.577 0.245 27.325)", + "border": "oklch(0.925 0.005 214.3)", + "input": "oklch(0.925 0.005 214.3)", + "ring": "oklch(0.723 0.014 214.4)", + "chart-1": "oklch(0.872 0.007 219.6)", + "chart-2": "oklch(0.56 0.021 213.5)", + "chart-3": "oklch(0.45 0.017 213.2)", + "chart-4": "oklch(0.378 0.015 216)", + "chart-5": "oklch(0.275 0.011 216.9)", + "sidebar": "oklch(0.987 0.002 197.1)", + "sidebar-foreground": "oklch(0.148 0.004 228.8)", + "sidebar-primary": "oklch(0.218 0.008 223.9)", + "sidebar-primary-foreground": "oklch(0.987 0.002 197.1)", + "sidebar-accent": "oklch(0.963 0.002 197.1)", + "sidebar-accent-foreground": "oklch(0.218 0.008 223.9)", + "sidebar-border": "oklch(0.925 0.005 214.3)", + "sidebar-ring": "oklch(0.723 0.014 214.4)", + }, + "dark": { + "background": "oklch(0.148 0.004 228.8)", + "foreground": "oklch(0.987 0.002 197.1)", + "card": "oklch(0.218 0.008 223.9)", + "card-foreground": "oklch(0.987 0.002 197.1)", + "popover": "oklch(0.218 0.008 223.9)", + "popover-foreground": "oklch(0.987 0.002 197.1)", + "primary": "oklch(0.925 0.005 214.3)", + "primary-foreground": "oklch(0.218 0.008 223.9)", + "secondary": "oklch(0.275 0.011 216.9)", + "secondary-foreground": "oklch(0.987 0.002 197.1)", + "muted": "oklch(0.275 0.011 216.9)", + "muted-foreground": "oklch(0.723 0.014 214.4)", + "accent": "oklch(0.275 0.011 216.9)", + "accent-foreground": "oklch(0.987 0.002 197.1)", + "destructive": "oklch(0.704 0.191 22.216)", + "border": "oklch(1 0 0 / 10%)", + "input": "oklch(1 0 0 / 15%)", + "ring": "oklch(0.56 0.021 213.5)", + "chart-1": "oklch(0.872 0.007 219.6)", + "chart-2": "oklch(0.56 0.021 213.5)", + "chart-3": "oklch(0.45 0.017 213.2)", + "chart-4": "oklch(0.378 0.015 216)", + "chart-5": "oklch(0.275 0.011 216.9)", + "sidebar": "oklch(0.218 0.008 223.9)", + "sidebar-foreground": "oklch(0.987 0.002 197.1)", + "sidebar-primary": "oklch(0.488 0.243 264.376)", + "sidebar-primary-foreground": "oklch(0.987 0.002 197.1)", + "sidebar-accent": "oklch(0.275 0.011 216.9)", + "sidebar-accent-foreground": "oklch(0.987 0.002 197.1)", + "sidebar-border": "oklch(1 0 0 / 10%)", + "sidebar-ring": "oklch(0.56 0.021 213.5)", + }, + }, +] diff --git a/app/templates/compiler.py b/app/templates/compiler.py new file mode 100644 index 00000000..8d7b36e7 --- /dev/null +++ b/app/templates/compiler.py @@ -0,0 +1,547 @@ +import reflex as rx + +from app.engine.actions import ADD_SWATCHES_JS, FORMAT_CSS_JS +from app.hooks import ( + css_output, + is_css_output_copied, + is_export_command_copied, + is_rxconfig_copied, + seed, + theme_export_method, + theme_preset_option, +) +from app.templates.config import rxconfig +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.dialog import dialog +from components.ui.tabs import tabs + +SYNC_BUTTON_STYLES_JS = """ + ({OPTIONS}).forEach(m => { + const btn = document.getElementById('{BUTTON_PREFIX}-btn-' + m); + const activeCheck = document.getElementById('{CHECK_ACTIVE_PREFIX}-' + m); + const inactiveCheck = document.getElementById('{CHECK_INACTIVE_PREFIX}-' + m); + + if (!btn) return; + + if (m === {TARGET_VALUE}) { + btn.classList.add('border-primary', 'dark:border-input', 'bg-secondary'); + btn.classList.remove('border-input/90'); + + activeCheck?.classList.replace('hidden', 'flex'); + inactiveCheck?.classList.replace('block', 'hidden'); + } else { + btn.classList.remove('border-primary', 'dark:border-input', 'bg-secondary'); + btn.classList.add('border-input/90'); + + activeCheck?.classList.replace('flex', 'hidden'); + inactiveCheck?.classList.replace('hidden', 'block'); + } + }); +""" + + +def select_radio_option_row( + title: str, description: str, group_name: str, value: str, default_on: bool = False +) -> rx.Component: + return rx.el.label( + rx.el.div( + rx.el.p(title, class_name="text-foreground font-medium text-sm"), + rx.el.p( + description, + class_name="text-muted-foreground max-w-[24rem] text-xs font-light leading-normal", + ), + class_name="flex flex-col gap-y-1", + ), + rx.el.div( + rx.el.input( + type="radio", + name=group_name, + value=value, + default_checked=default_on, + class_name="peer sr-only", + ), + rx.el.div( + rx.el.span( + "✓", + class_name="text-primary-foreground text-[11px] font-bold hidden", + ), + class_name=( + "flex size-4 items-center justify-center rounded-[4px] transition-colors " + "border border-input bg-transparent " + "peer-checked:bg-primary peer-checked:border-primary " + "[&_span]:peer-checked:block" + ), + ), + class_name="flex items-center", + ), + class_name=( + "w-full rounded-2xl px-4 py-2.5 flex flex-row items-center justify-between " + "text-left transition-all cursor-pointer border-1 " + "border-input/90 bg-transparent hover:bg-secondary/20 " + "has-[:checked]:border-primary has-[:checked]:dark:border-input has-[:checked]:bg-secondary/60" + ), + ) + + +def select_local_or_reflex_build_option( + title: str, + description: str, + method: str, +) -> rx.Component: + + sync_script = ( + SYNC_BUTTON_STYLES_JS.replace("{OPTIONS}", "['local', 'online']") + .replace("{BUTTON_PREFIX}", "method") + .replace("{CHECK_ACTIVE_PREFIX}", "method-checkmark-active") + .replace("{CHECK_INACTIVE_PREFIX}", "method-checkmark-inactive") + .replace("{TARGET_VALUE}", f"'{method}'") + ) + + click_script = f""" + sessionStorage.setItem("theme_export_method", "{method}"); + refs['_client_state_setTheme_export_method']('{method}'); + {sync_script} + """ + + return rx.el.button( + rx.el.div( + rx.el.div( + rx.el.div( + rx.el.p(title, class_name="text-foreground font-medium text-sm"), + rx.el.p( + description, + class_name="text-muted-foreground max-w-[24rem] text-xs font-light leading-normal", + ), + class_name="flex flex-col gap-y-1", + ), + rx.el.div( + rx.el.div(class_name="size-0 rounded-[4px] bg-primary-foreground"), + id=f"method-checkmark-active-{method}", + class_name="size-4 rounded-[4px] bg-primary hidden items-center justify-center", + ), + rx.el.div( + id=f"method-checkmark-inactive-{method}", + class_name="size-4 rounded-[4px] border border-input block", + ), + class_name="flex flex-row w-full items-center justify-between", + ), + class_name="!w-full flex flex-col gap-y-1", + ), + id=f"method-btn-{method}", + on_click=rx.call_script(click_script), + class_name="w-full rounded-2xl px-4 py-2.5 text-left text-sm transition-all border-1 border-input/90 flex items-center", + ) + + +def select_preset_option( + title: str, + description: str, + method: str, +) -> rx.Component: + + sync_script = ( + SYNC_BUTTON_STYLES_JS.replace("{OPTIONS}", "['full', 'theme']") + .replace("{BUTTON_PREFIX}", "preset") + .replace("{CHECK_ACTIVE_PREFIX}", "preset-checkmark-active") + .replace("{CHECK_INACTIVE_PREFIX}", "preset-checkmark-inactive") + .replace("{TARGET_VALUE}", f"'{method}'") + ) + + click_script = f""" + sessionStorage.setItem("theme_preset_option", "{method}"); + refs['_client_state_setTheme_preset_option']('{method}'); + {sync_script} + """ + + return rx.el.button( + rx.el.div( + rx.el.div( + rx.el.div( + rx.el.p(title, class_name="text-foreground font-medium text-sm"), + rx.el.p( + description, + class_name="text-muted-foreground max-w-[24rem] text-xs font-light leading-normal", + ), + class_name="flex flex-col gap-y-1", + ), + rx.el.div( + rx.el.div(class_name="size-0 rounded-[4px] bg-primary-foreground"), + id=f"preset-checkmark-active-{method}", + class_name="size-4 rounded-[4px] bg-primary hidden items-center justify-center", + ), + rx.el.div( + id=f"preset-checkmark-inactive-{method}", + class_name="size-4 rounded-[4px] border border-input block", + ), + class_name="flex flex-row w-full items-center justify-between", + ), + class_name="!w-full flex flex-col gap-y-1", + ), + id=f"preset-btn-{method}", + on_click=rx.call_script(click_script), + class_name="w-full rounded-2xl px-4 py-2.5 text-left text-sm transition-all border-1 border-input/90 flex items-center", + ) + + +def theme_cli_prompt() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.div( + rx.el.p( + rx.cond(theme_export_method.value == "local", "uv", "prompt"), + class_name="text-muted-foreground text-sm font-normal px-[1rem] py-2", + ), + rx.el.div( + rx.cond( + theme_export_method.value == "online", + rx.el.a( + "Open in Reflex Build", + class_name="text-xs font-light text-foreground", + href=f"https://build.reflex.dev/?prompt=Install and run pip install buridan-create and buridan init --preset {seed.value} --include {theme_preset_option.value} then add app = rx.App(stylesheets=['globals.css'])", + target="_blank", + rel="noopener noreferrer", + ), + ), + rx.el.button( + rx.cond( + is_export_command_copied.value, + hi( + "Tick01Icon", + class_name="size-4 text-foreground", + ), + hi( + "Copy01Icon", + class_name="size-4 text-foreground", + ), + ), + variant="outline", + class_name="pr-[1rem] py-2", + on_click=[ + rx.call_function(is_export_command_copied.set_value(True)), + rx.set_clipboard( + rx.cond( + theme_export_method.value == "local", + f"uv run buridan init --preset {seed.value} --include {theme_preset_option.value}", + f"https://build.reflex.dev/?prompt=Install and run pip install buridan-create and buridan init --preset {seed.value} --include {theme_preset_option.value} then add app = rx.App(stylesheets=['globals.css'])", + ), + ), + ], + on_mouse_down=rx.call_function( + is_export_command_copied.set_value(False) + ).debounce(1500), + ), + class_name="flex flex-row items-center gap-x-4", + ), + class_name="w-full border-b border-input flex flex-row items-center justify-between", + ), + rx.el.div( + rx.el.code( + rx.cond( + theme_export_method.value == "local", + f"uv run buridan init --preset {seed.value} --include {theme_preset_option.value} ", + f"Run this in the project terminal: `pip install buridan-create && buridan init --preset {seed.value} --include {theme_preset_option.value}` Then add this to your main app file: `app = rx.App(stylesheets=['globals.css'])`", + ), + style={ + "white-space": "pre", + "color": "var(--foreground)", + "font-size": "13px", + "padding": "1rem 1rem", + "display": "block", + }, + ), + class_name="overflow-x-auto overflow-y-auto scrollbar-none flex-1 min-h-0 pr-[1rem]", + ), + class_name="w-full flex-1 min-h-0 flex flex-col h-full", + ), + class_name="rounded-[0.625rem] outline outline-input flex-1 min-h-0 flex flex-col", + ) + + +def theme_export_compiler() -> rx.Component: + + open_script = f""" + const val = sessionStorage.getItem('theme_export_method') || 'local'; + const val_preset = sessionStorage.getItem('theme_preset_option') || 'full'; + + refs['_client_state_setTheme_export_method'](val); + + setTimeout(() => {{ + + { + SYNC_BUTTON_STYLES_JS.replace("{OPTIONS}", "['local', 'online']") + .replace("{BUTTON_PREFIX}", "method") + .replace("{CHECK_ACTIVE_PREFIX}", "method-checkmark-active") + .replace("{CHECK_INACTIVE_PREFIX}", "method-checkmark-inactive") + .replace("{TARGET_VALUE}", "val") + } + + }}, 30); + + refs['_client_state_setTheme_preset_option'](val_preset); + + setTimeout(() => {{ + + { + SYNC_BUTTON_STYLES_JS.replace("{OPTIONS}", "['full', 'theme']") + .replace("{BUTTON_PREFIX}", "preset") + .replace("{CHECK_ACTIVE_PREFIX}", "preset-checkmark-active") + .replace("{CHECK_INACTIVE_PREFIX}", "preset-checkmark-inactive") + .replace("{TARGET_VALUE}", "val_preset") + } + + }}, 30); + """ + + return dialog.root( + dialog.trigger( + button( + "Get Code", + variant="default", + class_name="w-full rounded-xl", + id="get-code-btn", + on_click=[rx.call_script(FORMAT_CSS_JS), rx.call_script(open_script)], + ), + class_name="w-full", + ), + dialog.portal( + dialog.backdrop(class_name="backdrop-blur-[5px]"), + dialog.popup( + rx.el.div( + tabs.root( + tabs.list( + tabs.indicator( + class_name="rounded-lg flex items-center justify-center" + ), + tabs.tab( + rx.el.p("New Project"), + value="project", + class_name="border-none flex items-center py-4", + on_click=rx.call_script(open_script), + ), + tabs.tab( + "Theme", + value="theme", + class_name="border-none flex items-center py-4", + on_click=rx.call_script(ADD_SWATCHES_JS), + ), + class_name="relative z-0 flex gap-1 rounded-none bg-transparent w-full mb-4", + ), + tabs.panel( + rx.el.div( + rx.el.div( + rx.el.p( + "Pick Export Method", + class_name="text-foreground text-sm font-normal", + ), + rx.el.p( + "Choose where you plan to use your theme.", + class_name="text-muted-foreground text-sm font-light pb-1", + ), + rx.el.div( + select_local_or_reflex_build_option( + title="Local Development", + description="Local Reflex app with your custom theme.", + method="local", + ), + select_local_or_reflex_build_option( + title="Reflex Build", + description="AI prompt matching your theme preset.", + method="online", + ), + class_name="w-full flex flex-col items-stretch gap-y-3 py-3", + ), + class_name="w-full flex flex-col gap-y-0", + ), + rx.el.div( + rx.el.p( + "Apply Preset", + class_name="text-foreground text-sm font-normal", + ), + rx.el.p( + "Pick which parts of the preset to apply.", + class_name="text-muted-foreground text-sm font-light pb-1", + ), + rx.el.div( + select_preset_option( + title="Full Preset", + description="All the preset, including components, theme, and fonts.", + method="full", + ), + select_preset_option( + title="Theme Tokens", + description="Theme tokens only, like colors and radii.", + method="theme", + ), + class_name="w-full flex flex-col items-stretch gap-y-3 py-3", + ), + class_name="w-full flex flex-col gap-y-0 py-3", + ), + rx.el.div( + theme_cli_prompt(), + class_name="w-full flex flex-col gap-y-0 py-3", + ), + class_name="w-full flex flex-col gap-y-0", + ), + value="project", + class_name="w-full", + ), + tabs.panel( + rx.el.div( + rx.el.div( + rx.el.div( + rx.el.p( + "Theme Tokens", + class_name="text-foreground text-sm font-normal", + ), + rx.el.p( + "Copy the CSS variables for this preset into your assets folder.", + class_name="text-muted-foreground text-sm font-light pb-2", + ), + class_name="w-full flex flex-col gap-y-1", + ), + rx.el.div( + rx.el.div( + rx.el.div( + rx.el.p( + "globals.css", + class_name="text-muted-foreground text-sm font-normal px-[0.75rem] py-2", + ), + rx.el.button( + rx.cond( + is_css_output_copied.value, + hi( + "Tick01Icon", + class_name="size-4 text-foreground", + ), + hi( + "Copy01Icon", + class_name="size-4 text-foreground", + ), + ), + variant="outline", + class_name="px-[0.75rem] py-2", + on_click=[ + rx.call_function( + is_css_output_copied.set_value( + True + ) + ), + rx.set_clipboard( + css_output.value + ), + ], + on_mouse_down=rx.call_function( + is_css_output_copied.set_value( + False + ) + ).debounce(1500), + ), + class_name="w-full border-b border-input flex flex-row items-center justify-between", + ), + rx.el.div( + rx.el.code( + css_output.value, + id="css-export-block", + style={ + "white-space": "pre", + "color": "var(--foreground)", + "font-size": "13px", + "padding": "1rem 0.75rem", + "display": "block", + }, + ), + class_name="overflow-x-auto overflow-y-auto scrollbar-none flex-1 min-h-0", + ), + class_name="w-full flex-1 min-h-0 flex flex-col h-full", + ), + class_name="rounded-[0.625rem] outline outline-input flex-1 min-h-0 flex flex-col", + ), + class_name="w-full flex flex-col gap-y-2 flex-1 min-h-0", + ), + rx.el.div( + rx.el.div( + rx.el.p( + "Reflex Configuration", + class_name="text-foreground text-sm font-normal", + ), + rx.el.p( + "Copy the extended tailwind config into your rxconfig.py file", + class_name="text-muted-foreground text-sm font-light pb-2", + ), + class_name="w-full flex flex-col gap-y-1", + ), + rx.el.div( + rx.el.div( + rx.el.div( + rx.el.p( + "rxconfig.py", + class_name="text-muted-foreground text-sm font-normal px-[0.75rem] py-2", + ), + rx.el.button( + rx.cond( + is_rxconfig_copied.value, + hi( + "Tick01Icon", + class_name="size-4 text-foreground", + ), + hi( + "Copy01Icon", + class_name="size-4 text-foreground", + ), + ), + variant="outline", + class_name="px-[0.75rem] py-2", + on_click=[ + rx.call_function( + is_rxconfig_copied.set_value( + True + ) + ), + rx.set_clipboard( + css_output.value + ), + ], + on_mouse_down=rx.call_function( + is_rxconfig_copied.set_value( + False + ) + ).debounce(1500), + ), + class_name="w-full border-b border-input flex flex-row items-center justify-between", + ), + rx.el.div( + rx.el.code( + rxconfig, + id="reflex-config-block", + style={ + "white-space": "pre", + "color": "var(--foreground)", + "font-size": "13px", + "padding": "1rem 0.75rem", + "display": "block", + }, + ), + class_name="overflow-x-auto overflow-y-auto scrollbar-none flex-1 min-h-0", + ), + class_name="w-full flex-1 min-h-0 flex flex-col h-full", + ), + class_name="rounded-[0.625rem] outline outline-input flex-1 min-h-0 flex flex-col", + ), + class_name="w-full flex flex-col gap-y-2 flex-1 min-h-0", + ), + class_name="w-full h-full flex flex-col gap-y-4 flex-1 min-h-0", + ), + class_name="w-full h-full flex flex-col flex-1 min-h-0", + value="theme", + ), + class_name="flex flex-col flex-1 min-h-0", + default_value="project", + ), + class_name="flex flex-col gap-y-4 !w-full h-full", + ), + class_name="!w-full max-w-md rounded-2xl dark bg-card h-[92vh] p-6 flex flex-col", + ), + ), + on_open_change=rx.call_script(open_script), + ) diff --git a/app/templates/config.py b/app/templates/config.py new file mode 100644 index 00000000..5870bc1a --- /dev/null +++ b/app/templates/config.py @@ -0,0 +1,66 @@ +rxconfig = """import reflex as rx +from reflex.plugins.shared_tailwind import TailwindConfig + +config = rx.Config( + plugins=[ + rx.plugins.TailwindV4Plugin( + TailwindConfig( + darkMode="class", + plugins=["@tailwindcss/typography", "tailwind-scrollbar"], + theme={ + "extend": { + "colors": { + "background": "var(--background)", + "foreground": "var(--foreground)", + "card": "var(--card)", + "card-foreground": "var(--card-foreground)", + "popover": "var(--popover)", + "popover-foreground": "var(--popover-foreground)", + "primary": "var(--primary)", + "primary-foreground": "var(--primary-foreground)", + "secondary": "var(--secondary)", + "secondary-foreground": "var(--secondary-foreground)", + "muted": "var(--muted)", + "muted-foreground": "var(--muted-foreground)", + "accent": "var(--accent)", + "accent-foreground": "var(--accent-foreground)", + "destructive": "var(--destructive)", + "border": "var(--border)", + "input": "var(--input)", + "ring": "var(--ring)", + "chart-1": "var(--chart-1)", + "chart-2": "var(--chart-2)", + "chart-3": "var(--chart-3)", + "chart-4": "var(--chart-4)", + "chart-5": "var(--chart-5)", + "sidebar": "var(--sidebar)", + "sidebar-foreground": "var(--sidebar-foreground)", + "sidebar-primary": "var(--sidebar-primary)", + "sidebar-primary-foreground": "var(--sidebar-primary-foreground)", + "sidebar-accent": "var(--sidebar-accent)", + "sidebar-accent-foreground": "var(--sidebar-accent-foreground)", + "sidebar-border": "var(--sidebar-border)", + "sidebar-ring": "var(--sidebar-ring)", + }, + "fontFamily": { + "theme": "var(--font-family)", + }, + "borderRadius": { + "radius": "var(--radius)", + }, + "padding": { + "card": "var(--card-padding)", + }, + "gap": { + "card": "var(--card-gap)", + }, + "boxShadow": { + "default": "var(--shadow)", + }, + } + }, + ) + ), + ], +) +""" diff --git a/app/templates/docpage.py b/app/templates/docpage.py new file mode 100644 index 00000000..36a54166 --- /dev/null +++ b/app/templates/docpage.py @@ -0,0 +1,35 @@ +import reflex as rx + +from app.templates.docsidebar import sidebar +from app.templates.footer import footer +from app.templates.navbar import navbar + + +def docpage(main_content, toc_content): + """The template for all documentation pages.""" + return rx.el.div( + rx.el.header(navbar(), class_name="sticky top-0 z-50"), + rx.el.main( + rx.el.div( + sidebar(), + rx.el.div( + rx.el.div( + rx.el.div( + main_content, + class_name="mx-auto flex w-full max-w-[40rem] min-w-0 flex-1 px-2 py-6 md:px-0 lg:py-8", + ), + class_name="flex-1 min-w-0", + ), + toc_content, + class_name="flex items-start w-full flex-1 min-w-0", + ), + class_name="flex w-full gap-x-0 xl:max-w-[96rem] 2xl:max-w-[96rem] mx-auto px-7", + ), + class_name="w-full", + ), + rx.el.footer( + footer(), + class_name="w-full flex items-center justify-center py-8 text-muted-foreground !text-sm !text-center", + ), + class_name="bg-background relative flex min-h-screen flex-col", + ) diff --git a/src/templates/sidebar.py b/app/templates/docsidebar.py similarity index 54% rename from src/templates/sidebar.py rename to app/templates/docsidebar.py index 139a5a52..9ece96aa 100644 --- a/src/templates/sidebar.py +++ b/app/templates/docsidebar.py @@ -3,8 +3,8 @@ import reflex as rx -import src.hooks as hooks -import src.routes as routes +import app.utils.routes as routes +from components.ui.button import button @dataclass @@ -12,53 +12,31 @@ class SidebarSection: """Configuration for a sidebar section.""" title: str - description: str routes: list[dict] SIDEBAR_SECTIONS = [ - SidebarSection( - title="Getting Started", - description="Quickly set up and get started with the basics of buridan/ui.", - routes=routes.GET_STARTED_URLS, - ), - SidebarSection( - title="Wrapped React Components", - description="Explore React components elegantly wrapped for Reflex", - routes=routes.WRAPPED_COMPONENTS_URLS, - ), - SidebarSection( - title="JavaScript Integrations", - description="Learn how to extend Reflex apps with native JavaScript libraries.", - routes=routes.JS_INTEGRATIONS_URLS, - ), - SidebarSection( - title="Charts", - description="A collection of chart components to help visualize data, build dashboards, and more.", - routes=routes.CHARTS_URLS, - ), - SidebarSection( - title="Components", - description="Core components to help you build beautiful and visually consistent applications.", - routes=routes.BASE_UI_COMPONENTS, - ), + SidebarSection(title="Getting Started", routes=routes.GET_STARTED_URLS), + SidebarSection(title="Charts", routes=routes.CHARTS_URLS), + SidebarSection(title="Components", routes=routes.BASE_UI_COMPONENTS), ] def create_menu_item(data: dict, in_drawer): """Create a single menu item.""" - return rx.el.div( + return button( rx.el.a( rx.el.p( data["title"], - class_name="cursor-pointer font-[450] " + class_name="cursor-pointer" + rx.cond(in_drawer, "text-lg px-2", "text-sm").to(str), ), to=f"/{data['url']}", text_decoration="none", - on_click=hooks.menu_icon.set_value(False), ), - class_name="w-full", + variant="ghost", + size="sm", + class_name="w-fit", id=data["url"], ) @@ -67,7 +45,7 @@ def create_sidebar_menu_items(routes: List[dict], in_drawer): """Create menu items from routes.""" return rx.el.div( *[create_menu_item(route, in_drawer) for route in routes], - class_name="w-full flex flex-col gap-y-1.5", + class_name="w-full flex flex-col gap-y-0 justify-start", ) @@ -89,7 +67,7 @@ def sidebar_section(section: SidebarSection, in_drawer=False): rx.el.div( rx.el.p( section.title, - class_name="text-muted-foreground font-medium " + class_name="text-muted-foreground font-medium px-2 " + rx.cond(in_drawer, "text-md px-2", "text-xs").to(str), ), class_name="flex flex-row items-center gap-x-2", @@ -97,26 +75,29 @@ def sidebar_section(section: SidebarSection, in_drawer=False): class_name="w-full flex flex-row justify-between align-center items-center", ), create_section_content(section, in_drawer), - class_name="flex flex-col w-full gap-y-2 p-4", + class_name="flex flex-col w-full gap-y-2 py-4", ) def sidebar(in_drawer=False): """Main sidebar component.""" content = rx.el.div( + rx.el.div(class_name="py-5"), *[sidebar_section(section, in_drawer) for section in SIDEBAR_SECTIONS], + rx.el.div(class_name="py-5"), class_name="flex flex-col max-w-[18rem] w-full h-full", ) - drawer_classes = "flex flex-col w-full h-full" - default_classes = ( - "hidden lg:flex max-w-[18rem] w-full sticky top-18 max-h-[100vh] z-[10] pb-5" - ) - return rx.el.div( - rx.el.div( - content, - class_name="flex flex-col items-center gap-y-4 overflow-scroll scrollbar-none justify-start items-start", + rx.el.div(content, id="sidebar"), + class_name=( + "hidden lg:flex flex-col " + "max-w-[18rem] w-full " + "sticky top-32 " + "h-[calc(100svh-16rem)] " + "overflow-y-auto scrollbar-none " + "sm:mask-[linear-gradient(to_bottom,transparent_0%,black_15%,black_85%,transparent_100%)] " + "sm:mask-size-[100%_100%] " + "sm:mask-repeat-no-repeat " ), - class_name=drawer_classes if in_drawer else default_classes, ) diff --git a/app/templates/footer.py b/app/templates/footer.py new file mode 100644 index 00000000..902c9dde --- /dev/null +++ b/app/templates/footer.py @@ -0,0 +1,26 @@ +import reflex as rx + + +def footer() -> rx.Component: + return rx.el.p( + "Built by ", + rx.el.a( + "Line Indent", + href="https://github.com/LineIndent", + class_name="font-semibold underline", + ), + " at ", + rx.el.a( + "Reflex", + href="https://reflex.dev", + class_name="font-semibold underline", + ), + ". The source code is available on ", + rx.el.a( + "GitHub", + href="https://github.com/buridan-ui/ui", + class_name="font-semibold underline", + ), + ".", + class_name="text-[13px] font-light", + ) diff --git a/app/templates/layout.py b/app/templates/layout.py new file mode 100644 index 00000000..edb7e3a3 --- /dev/null +++ b/app/templates/layout.py @@ -0,0 +1,73 @@ +import functools + +import reflex as rx + +from app.templates.footer import footer +from app.templates.navbar import navbar + + +def layout_decorator( + title: str, + description: str, + ctas: list | None = None, + with_create_page_cta: bool = False, +): + """ + Decorator to wrap a page function with a sticky navbar, + a landing header, and page-specific content. + """ + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + page_content = func(*args, **kwargs) + + return rx.el.body( + rx.el.div( + rx.el.header( + navbar(with_create_page_cta=with_create_page_cta), + class_name="sticky top-0 z-50 w-full bg-background", + ), + rx.el.main( + rx.el.div( + rx.el.section( + rx.el.div( + rx.el.div( + rx.el.h1( + title, + class_name="leading-tighter text-3xl font-semibold tracking-tight text-balance text-primary lg:leading-[1.1] lg:font-semibold xl:text-5xl xl:tracking-tighter max-w-4xl", + ), + rx.el.p( + description, + class_name="max-w-4xl text-base text-balance text-foreground sm:text-lg", + ), + rx.el.div( + *(ctas if ctas else []), + class_name="flex w-full items-center justify-center gap-2 pt-2 **:data-[slot=button]:shadow-none", + ), + class_name="flex flex-col items-center gap-2 px-6 py-8 text-center md:py-16 lg:py-20 xl:gap-4", + ), + class_name="w-full", + ), + class_name="border-grid pb-6", + ), + rx.el.div( + page_content, + class_name="flex-1 p-0 bg-background", + ), + class_name="flex flex-1 flex-col bg-background", + ), + class_name="flex min-h-0 flex-1 flex-col pb-6", + ), + rx.el.footer( + footer(), + class_name="w-full flex items-center justify-center py-8 text-muted-foreground !text-sm !text-center", + ), + class_name="relative z-10 flex min-h-svh flex-col bg-background overscroll-none", + ), + class_name="font-theme bg-background", + ) + + return wrapper + + return decorator diff --git a/app/templates/mainpage.py b/app/templates/mainpage.py new file mode 100644 index 00000000..5880fe4b --- /dev/null +++ b/app/templates/mainpage.py @@ -0,0 +1,26 @@ +import reflex as rx + +from app.engine.actions import INITIAL_LOAD_JS +from app.engine.seed import seed_engine +from app.engine.url_sync import url_sync_engine +from app.templates.navbar import navbar +from app.templates.preview import preview +from app.templates.sidebar import sidebar +from app.templates.welcome import welcome_dialog + +DIV = "relative flex h-screen flex-col bg-background overflow-hidden scrollbar-none" +MAIN = "flex flex-col gap-x-6 lg:flex-row w-full h-full min-h-0 overflow-hidden p-4 lg:px-6 lg:pb-6 lg:pt-2 gap-y-6 scrollbar-none" + + +def mainpage() -> rx.Component: + return rx.el.body( + seed_engine(), + url_sync_engine(), + welcome_dialog(), + rx.el.div( + navbar(), + rx.el.main(sidebar(), preview(), class_name=MAIN), + class_name=DIV, + on_mount=rx.call_script(INITIAL_LOAD_JS), + ), + ) diff --git a/app/templates/navbar.py b/app/templates/navbar.py new file mode 100644 index 00000000..b5285a28 --- /dev/null +++ b/app/templates/navbar.py @@ -0,0 +1,131 @@ +import reflex as rx + +from app.engine.actions import TOGGLE_DARK_JS +from app.hooks import seed, theme_preset_option +from components.icons.hugeicon import hi +from components.ui.button import button + +NAV_LIST = [ + {"name": "Home", "path": "/"}, + {"name": "Docs", "path": "/docs/getting-started/introduction"}, + {"name": "Components", "path": "/components"}, + {"name": "Charts", "path": "/charts"}, + {"name": "Create", "path": "/create"}, +] + + +def icon_wrapper(svg_str: str, class_name: str = "size-5"): + return rx.el.div(rx.html(svg_str), class_name=f"flex items-center {class_name}") + + +def separator(): + return rx.el.p("︲", class_name="text-muted-foreground/50 font-thin hidden lg:flex") + + +def open_in_reflex_build() -> rx.Component: + svg = """ + + + """ + return rx.el.a( + button( + rx.el.div( + rx.el.p("Open in", class_name="text-sm"), + icon_wrapper(svg), + class_name="flex flex-row items-center gap-x-2", + ), + variant="outline", + size="sm", + ), + href=f"https://build.reflex.dev/?prompt=Install and run pip install buridan-create and buridan init --preset {seed.value} --include {theme_preset_option.value} then add app = rx.App(stylesheets=['globals.css'])", + target="_blank", + ) + + +def light_and_dark_toggle() -> rx.Component: + svg = """ + + + + + + + """ + return button( + icon_wrapper(svg, "size-4"), + # on_click=rx.call_script(TOGGLE_DARK_JS), + on_click=rx.toggle_color_mode, + variant="ghost", + size="sm", + ) + + +def site_github() -> rx.Component: + svg = """""" + return rx.el.a( + button( + icon_wrapper(svg), + rx.el.p("241", class_name="text-sm text-muted-foreground"), + variant="ghost", + size="sm", + class_name="text-foreground", + ), + href="https://github.com/buridan-ui", + class_name="no-underline", + ) + + +def navbar(with_create_page_cta: bool = False) -> rx.Component: + actions = [ + site_github(), + separator(), + light_and_dark_toggle(), + separator(), + rx.el.a( + button(hi("PlusSignIcon", class_name="size-4"), "New", size="sm"), + href="/create", + ), + ] + + if with_create_page_cta: + actions.extend( + [ + open_in_reflex_build(), + button( + "Get Code", + on_click=rx.call_script( + "document.getElementById('get-code-btn')?.click()" + ), + size="sm", + ), + ] + ) + + return rx.el.header( + rx.el.div( + # Links + rx.el.div( + *[ + rx.el.a( + button(item["name"], variant="ghost", size="sm"), + href=item["path"], + # target="_blank", + ) + for item in NAV_LIST + ], + class_name="hidden lg:flex flex-row items-center text-foreground", + ), + rx.el.div(*actions, class_name="flex flex-row gap-x-2 items-center"), + class_name="w-full max-w-[96rem] mx-auto flex flex-row items-center justify-between px-7", + ), + class_name="sticky top-0 z-50 w-full h-13 bg-background flex items-center", + ) diff --git a/app/templates/options.py b/app/templates/options.py new file mode 100644 index 00000000..5354b9af --- /dev/null +++ b/app/templates/options.py @@ -0,0 +1,706 @@ +import reflex as rx + +from app.engine.actions import ( + APPLY_BASE_CHARTS_JS, + APPLY_BASE_PRIMARY_JS, + APPLY_SEED_JS, + SHUFFLE_JS, + TOGGLE_DARK_JS, + _apply_base_theme_js, + _apply_chart_color_js, + _apply_color_theme_js, + _apply_font_js, + _apply_style_js, + _patch_radius_js, +) +from app.hooks import ( + base_theme_color, + chart_color, + copy_preset_value, + seed, + selected_base_color_cs, + selected_chart_cs, + selected_component_category, + selected_font_cs, + selected_radius_cs, + selected_style_cs, + selected_theme_cs, + theme_color, +) +from app.registry.colors import COLOR_THEMES +from app.registry.fonts import FONT_REGISTRY +from app.registry.radii import RADIUS_OPTIONS +from app.registry.styles import STYLE_REGISTRY +from app.registry.themes import BASE_THEMES +from app.templates.compiler import theme_export_compiler +from app.templates.reset import reset_theme_button +from components.icons.hugeicon import hi +from components.ui.button import button +from components.ui.dialog import dialog +from components.ui.input import input +from components.ui.select import select + + +def radius_icon() -> rx.Component: + return rx.html( + """ + + + + """ + ) + + +def style_icon() -> rx.Component: + return rx.html( + """ + + + + """ + ) + + +def component_panel(desktop: bool = True) -> rx.Component: + return select.root( + select.trigger( + rx.el.div( + rx.el.div( + rx.el.p("Components", class_name="text-muted-foreground text-xs"), + rx.el.p(select.value(), class_name="text-primary"), + class_name="flex flex-col items-start", + ), + hi("KeyframesMultipleIcon", class_name="size-5"), + class_name="w-full flex flex-row justify-between items-center", + ), + class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + ), + select.portal( + select.positioner( + select.popup( + select.group( + select.item( + select.item_text("All"), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value="All", + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=selected_component_category.set_value("All"), + ), + select.item( + select.item_text("Tech"), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value="Tech", + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=selected_component_category.set_value("Tech"), + ), + select.item( + select.item_text("General"), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value="General", + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=selected_component_category.set_value("General"), + ), + select.item( + select.item_text("Healthcare"), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value="Healthcare", + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=selected_component_category.set_value( + "Healthcare" + ), + ), + select.item( + select.item_text("Finance"), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value="Finance", + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=selected_component_category.set_value("Finance"), + ), + class_name="p-1", + ), + class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" + if desktop + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + ), + side_offset=25, + side="right" if desktop else "top", + align="start", + class_name="" if desktop else "w-full pl-3 pr-5", + ), + ), + name="component_select", + value=selected_component_category.value, + on_value_change=selected_component_category.set_value, + ) + + +def menu_panel(desktop: bool = True) -> rx.Component: + return select.root( + select.trigger( + rx.el.p("Menu", class_name="text-primary"), + hi("EqualSignIcon", class_name="size-5"), + class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + ), + select.portal( + select.positioner( + select.popup( + select.group( + select.item( + select.item_text("Open Preset"), + on_click=rx.call_script( + "document.getElementById('preset-trigger-btn')?.click()" + ), + ), + select.item( + select.item_text("Shuffle"), + on_click=rx.call_script(SHUFFLE_JS), + ), + select.item( + select.item_text("Light/Dark"), + on_click=rx.call_script(TOGGLE_DARK_JS), + ), + select.separator(), + select.item(reset_theme_button()), + class_name="p-1", + ), + class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl " + + "w-[250px]" + if desktop + else "", + ), + side_offset=25, + side="right" if desktop else "top", + align="start", + class_name="" if desktop else "w-full", + ), + ), + name="radius_select", + default_value="Medium", + ) + + +def style_panel(desktop: bool = True) -> rx.Component: + return select.root( + select.trigger( + rx.el.div( + rx.el.div( + rx.el.p("Style", class_name="text-muted-foreground text-xs"), + rx.el.p(select.value(), class_name="text-primary"), + class_name="flex flex-col items-start", + ), + style_icon(), + class_name="w-full flex flex-row justify-between items-center", + ), + class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + ), + select.portal( + select.positioner( + select.popup( + select.group( + *[ + select.item( + select.item_text(s["label"]), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value=s["label"], + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=rx.call_script(_apply_style_js(s["id"])), + ) + for s in STYLE_REGISTRY + ], + class_name="p-1", + ), + class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" + if desktop + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + ), + side_offset=25, + side="right" if desktop else "top", + align="start", + class_name="" if desktop else "w-full pl-3 pr-5", + ), + ), + name="style_select", + value=selected_style_cs.value, + on_value_change=selected_style_cs.set_value, + ) + + +def font_panel(desktop: bool = True) -> rx.Component: + categories = {"sans": "Sans Serif", "serif": "Serif", "mono": "Monospace"} + + return select.root( + select.trigger( + rx.el.div( + rx.el.div( + rx.el.p("Font Family", class_name="text-muted-foreground text-xs"), + rx.el.p(select.value(), class_name="text-primary"), + class_name="flex flex-col items-start", + ), + hi("TextFontIcon", class_name="size-4"), + class_name="w-full flex flex-row justify-between items-center", + ), + class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + ), + select.portal( + select.positioner( + select.popup( + *[ + select.group( + select.group_label( + categories[cat_id], + class_name="text-[10px] font-semibold uppercase", + ), + *[ + select.item( + select.item_text(f["label"]), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value=f["label"], + class_name="w-full flex flex-row items-center justify-between rounded-lg px-2 py-1.5 text-sm cursor-pointer hover:bg-accent hover:text-accent-foreground", + on_click=rx.call_script(_apply_font_js(f["id"])), + ) + for f in FONT_REGISTRY + if f.get("category") == cat_id + ], + class_name="p-1", + ) + for cat_id in categories + ], + class_name="h-[40vh] overflow-y-auto rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px] scrollbar-none" + if desktop + else "h-[40vh] overflow-y-auto rounded-xl border-0 dark bg-card/90 backdrop-blur-xl scrollbar-none", + ), + side_offset=25, + side="right" if desktop else "top", + align="start", + class_name="" if desktop else "w-full pl-3 pr-5", + ), + ), + name="font_select", + value=selected_font_cs.value, + on_value_change=selected_font_cs.set_value, + ) + + +def base_color_panel(desktop: bool = True) -> rx.Component: + rows = [BASE_THEMES[i : i + 3] for i in range(0, len(BASE_THEMES), 3)] + + return select.root( + select.trigger( + rx.el.div( + rx.el.div( + rx.el.p("Base Color", class_name="text-muted-foreground text-xs"), + rx.el.p(select.value(), class_name="text-primary"), + class_name="flex flex-col items-start", + ), + rx.el.div( + style={"backgroundColor": base_theme_color.value}, + class_name="size-4 rounded-full", + ), + class_name="w-full flex flex-row justify-between items-center", + ), + class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + ), + select.portal( + select.positioner( + select.popup( + select.group( + *[ + select.item( + select.item_text(t["label"]), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value=t["label"], + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=[ + base_theme_color.set_value(t["light"]["ring"]), + selected_base_color_cs.set_value(t["label"]), + rx.call_script(_apply_base_theme_js(t["id"])), + ], + ) + for row in rows + for t in row + ], + class_name="p-1", + ), + class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" + if desktop + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + ), + side_offset=25, + side="right" if desktop else "top", + align="start", + class_name="" if desktop else "w-full pl-3 pr-5", + ), + ), + name="base_color_select", + value=selected_base_color_cs.value, + on_value_change=selected_base_color_cs.set_value, + ) + + +def theme_panel(desktop: bool = True) -> rx.Component: + # rows = [COLOR_THEMES[i : i + 3] for i in range(0, len(COLOR_THEMES), 3)] + + sorted_themes = sorted(COLOR_THEMES, key=lambda x: x["label"]) + + rows = [sorted_themes[i : i + 3] for i in range(0, len(sorted_themes), 3)] + + return select.root( + select.trigger( + rx.el.div( + rx.el.div( + rx.el.p("Theme", class_name="text-muted-foreground text-xs"), + rx.el.p( + rx.cond( + theme_color.value, + select.value(), + selected_base_color_cs.value, + ), + class_name="text-primary", + ), + class_name="flex flex-col items-start", + ), + rx.el.div( + style={ + "backgroundColor": rx.cond( + theme_color.value, theme_color.value, base_theme_color.value + ) + }, + class_name="size-4 rounded-full", + ), + class_name="w-full flex flex-row justify-between items-center", + ), + class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + ), + select.portal( + select.positioner( + select.popup( + select.group( + select.item( + select.item_text(selected_base_color_cs.value), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value=selected_base_color_cs.value, + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=[ + theme_color.set_value(""), + rx.call_script(APPLY_BASE_PRIMARY_JS), + ], + ), + select.separator(), + *[ + select.item( + select.item_text(t["label"]), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value=t["label"], + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=[ + theme_color.set_value(t["light"]["primary"]), + rx.call_script(_apply_color_theme_js(t["id"])), + ], + ) + for row in rows + for t in row + ], + class_name="p-1", + ), + class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" + if desktop + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + ), + side_offset=25, + side="right" if desktop else "top", + align="start", + class_name="" if desktop else "w-full pl-3 pr-5", + ), + ), + name="theme_panel", + value=selected_theme_cs.value, + on_value_change=selected_theme_cs.set_value, + ) + + +def chart_color_panel(desktop: bool = True) -> rx.Component: + # rows = [COLOR_THEMES[i : i + 3] for i in range(0, len(COLOR_THEMES), 3)] + + sorted_themes = sorted(COLOR_THEMES, key=lambda x: x["label"]) + + rows = [sorted_themes[i : i + 3] for i in range(0, len(sorted_themes), 3)] + + return select.root( + select.trigger( + rx.el.div( + rx.el.div( + rx.el.p("Chart Color", class_name="text-muted-foreground text-xs"), + rx.el.p( + rx.cond( + chart_color.value, + select.value(), + selected_base_color_cs.value, + ), + class_name="text-primary", + ), + class_name="flex flex-col items-start", + ), + rx.el.div( + style={ + "backgroundColor": rx.cond( + chart_color.value, chart_color.value, base_theme_color.value + ) + }, + class_name="size-4 rounded-full", + ), + class_name="w-full flex flex-row justify-between items-center", + ), + class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + ), + select.portal( + select.positioner( + select.popup( + select.group( + select.item( + select.item_text(selected_base_color_cs.value), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value=selected_base_color_cs.value, + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=[ + chart_color.set_value(""), + rx.call_script(APPLY_BASE_CHARTS_JS), + ], + ), + select.separator(), + *[ + select.item( + select.item_text(t["label"]), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value=t["label"], + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=[ + chart_color.set_value(t["light"]["primary"]), + rx.call_script(_apply_chart_color_js(t["id"])), + ], + ) + for row in rows + for t in row + ], + class_name="p-1", + ), + class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" + if desktop + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + ), + side_offset=25, + side="right" if desktop else "top", + align="start", + class_name="" if desktop else "w-full pl-3 pr-5", + ), + ), + name="chart_color_panel", + value=selected_chart_cs.value, + on_value_change=selected_chart_cs.set_value, + ) + + +def radius_panel(desktop: bool = True) -> rx.Component: + return select.root( + select.trigger( + rx.el.div( + rx.el.div( + rx.el.p("Radius", class_name="text-muted-foreground text-xs"), + rx.el.p(select.value(), class_name="text-primary"), + class_name="flex flex-col items-start", + ), + radius_icon(), + class_name="w-full flex flex-row justify-between items-center", + ), + class_name="w-full rounded-xl p-2 !bg-transparent hover:!bg-secondary", + ), + select.portal( + select.positioner( + select.popup( + select.group( + *[ + select.item( + select.item_text(label), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value=label, + class_name=( + "w-full flex flex-row items-center justify-between rounded-lg" + ), + on_click=rx.call_script(_patch_radius_js(value)), + ) + for label, value in RADIUS_OPTIONS + ], + class_name="p-1", + ), + class_name="rounded-xl border-0 dark bg-card/90 backdrop-blur-xl w-[250px]" + if desktop + else "rounded-xl border-0 dark bg-card/90 backdrop-blur-xl", + ), + side_offset=25, + side="right" if desktop else "top", + align="start", + class_name="" if desktop else "w-full pl-3 pr-5", + ), + ), + name="radius_select", + value=selected_radius_cs.value, + ) + + +def shuffle_button() -> rx.Component: + return button( + "Shuffle", + variant="outline", + class_name="w-full rounded-xl !bg-transparent hover:!bg-secondary", + on_click=rx.call_script(SHUFFLE_JS), + ) + + +def preset_copy_button() -> rx.Component: + return button( + rx.cond( + copy_preset_value.value, + "Copied", + f"--preset {seed.value}", + ), + variant="outline", + class_name="w-full rounded-xl !bg-transparent hover:!bg-secondary", + on_click=[ + rx.call_function(copy_preset_value.set_value(True)), + rx.set_clipboard(f"{seed.value}"), + ], + on_mouse_down=rx.call_function(copy_preset_value.set_value(False)).debounce( + 1500 + ), + ) + + +def open_preset_menu() -> rx.Component: + return dialog.root( + dialog.trigger( + button( + "Open Preset", + variant="outline", + class_name="w-full rounded-xl !bg-transparent hover:!bg-secondary", + id="preset-trigger-btn", + ), + class_name="w-full", + ), + dialog.portal( + dialog.backdrop(class_name="backdrop-blur-[5px]"), + dialog.popup( + rx.el.div( + rx.el.div( + rx.el.p( + "Open Theme Preset", + class_name="text-foreground text-sm font-normal", + ), + rx.el.p( + "Paste a preset ID to load a theme configuration.", + class_name="text-muted-foreground text-sm font-light", + ), + class_name="w-full flex flex-col gap-y-1", + ), + input( + placeholder="ex: b2D0wqNxT", + id="seed-input-el", + class_name="rounded-radius text-foreground", + ), + rx.el.div( + dialog.close( + button("Cancel", variant="outline", class_name="w-full"), + class_name="flex-1", + ), + dialog.close( + button( + "Open", + class_name="w-full", + on_click=rx.call_script(APPLY_SEED_JS), + ), + class_name="flex-1", + ), + class_name="flex flex-row gap-x-6", + ), + class_name="flex flex-col gap-y-4 w-full", + ), + class_name="!w-full max-w-sm rounded-2xl dark bg-card p-5 flex flex-col", + ), + ), + ) + + +def get_code_menu() -> rx.Component: + return theme_export_compiler() diff --git a/app/templates/preview.py b/app/templates/preview.py new file mode 100644 index 00000000..854eff93 --- /dev/null +++ b/app/templates/preview.py @@ -0,0 +1,92 @@ +import reflex as rx + +from app.examples.components import ( + card_eight, + card_eighteen, + card_eleven, + card_fifteen, + card_five, + card_four, + card_fourteen, + card_nine, + card_nineteen, + card_one, + card_seven, + card_seventeen, + card_six, + card_sixteen, + card_ten, + card_thirteen, + card_three, + card_twelve, + card_twenty, + card_twenty_eight, + card_twenty_five, + card_twenty_four, + card_twenty_nine, + card_twenty_one, + card_twenty_seven, + card_twenty_six, + card_twenty_three, + card_twenty_two, + card_two, +) +from app.hooks import theme + + +def preview() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.div( + rx.el.div( + rx.el.div( + card_ten(), + card_fourteen(), + card_six(), + card_thirteen(), + card_sixteen(), + card_eleven(), + card_four(), + card_twelve(), + card_eight(), + card_seven(), + card_nineteen(), + card_eighteen(), + card_twenty(), + card_seventeen(), + card_fifteen(), + card_nine(), + card_five(), + card_three(), + card_two(), + card_one(), + card_twenty_six(), + card_twenty_three(), + card_twenty_five(), + card_twenty_eight(), + card_twenty_nine(), + card_twenty_one(), + card_twenty_seven(), + card_twenty_two(), + card_twenty_four(), + class_name=" ".join( + [ + "mx-auto", + "columns-1", + "sm:columns-3", + "md:columns-3", + "lg:columns-4", + "gap-10", + "space-y-10", + ] + ), + ), + class_name="sm:min-w-[1600px] w-full", + ), + class_name="w-full h-full border-1 border-input/90 rounded-2xl p-4 md:p-10 bg-secondary dark:bg-transparent overflow-auto scrollbar-none", + ), + class_name="w-full h-full", + ), + class_name="w-full flex-[2] min-h-0 order-first lg:order-none lg:flex-1 lg:min-w-0 lg:h-full", + style=theme.value.to(dict), + ) diff --git a/app/templates/reset.py b/app/templates/reset.py new file mode 100644 index 00000000..04c49326 --- /dev/null +++ b/app/templates/reset.py @@ -0,0 +1,55 @@ +import reflex as rx + +from app.engine.actions import RESET_JS +from app.hooks import selected_component_category +from components.ui.button import button +from components.ui.dialog import dialog + + +def reset_theme_button() -> rx.Component: + """Reset theme button with confirmation dialog.""" + return dialog.root( + dialog.trigger( + rx.el.p("Reset", class_name="text-start"), + class_name="w-full", + ), + dialog.portal( + dialog.backdrop(class_name="backdrop-blur-[5px]"), + dialog.popup( + rx.el.div( + rx.el.div( + rx.el.p( + "Reset Theme", + class_name="text-foreground text-sm font-normal", + ), + rx.el.p( + "Are you sure you want to reset to the default theme? This will clear all your current customizations.", + class_name="text-muted-foreground text-sm font-light", + ), + class_name="w-full flex flex-col gap-y-1", + ), + rx.el.div( + dialog.close( + button("Cancel", variant="outline", class_name="w-full"), + class_name="flex-1", + ), + dialog.close( + button( + "Reset", + variant="destructive", + class_name="w-full", + on_click=[ + rx.call_script(RESET_JS), + selected_component_category.set_value("All"), + ], + ), + class_name="flex-1", + ), + class_name="flex flex-row gap-x-6", + ), + class_name="flex flex-col gap-y-4 w-full", + ), + class_name="!w-full max-w-sm rounded-2xl dark bg-card p-5 flex flex-col", + ), + ), + ) diff --git a/app/templates/sidebar.py b/app/templates/sidebar.py new file mode 100644 index 00000000..98196ad3 --- /dev/null +++ b/app/templates/sidebar.py @@ -0,0 +1,113 @@ +import reflex as rx + +from app.hooks import ( + base_theme_color, + chart_color, + copy_preset_value, + css_output, + darkmode, + seed, + selected_base_color_cs, + selected_chart_cs, + selected_font_cs, + selected_radius_cs, + selected_style_cs, + selected_theme_cs, + theme, + theme_color, + theme_export_method, + theme_preset_option, +) +from app.templates.options import ( + base_color_panel, + chart_color_panel, + component_panel, + font_panel, + get_code_menu, + menu_panel, + open_preset_menu, + preset_copy_button, + radius_panel, + shuffle_button, + style_panel, + theme_panel, +) + + +def sidebar_mobile() -> rx.Component: + return rx.el.div( + rx.el.div( + rx.el.div(style_panel(desktop=False), class_name="p-3 w-[12rem] shrink-0"), + rx.el.div( + base_color_panel(desktop=False), class_name="p-3 w-[12rem] shrink-0" + ), + rx.el.div(theme_panel(desktop=False), class_name="p-3 w-[12rem] shrink-0"), + rx.el.div( + chart_color_panel(desktop=False), class_name="p-3 w-[12rem] shrink-0" + ), + rx.el.div(font_panel(desktop=False), class_name="p-3 w-[12rem] shrink-0"), + rx.el.div(radius_panel(desktop=False), class_name="p-3 w-[12rem] shrink-0"), + rx.el.div( + component_panel(desktop=False), class_name="p-3 w-[12rem] shrink-0" + ), + class_name="flex flex-row overflow-x-auto scrollbar-none w-full min-w-0", + ), + rx.el.div( + rx.el.div(preset_copy_button(), class_name="w-full sm:w-1/2 shrink-0"), + rx.el.div(open_preset_menu(), class_name="w-full sm:w-1/3 shrink-0"), + rx.el.div(shuffle_button(), class_name="w-full sm:w-1/3 shrink-0"), + class_name="p-3 flex items-center gap-3 bg-secondary/50 border-t border-input/90 overflow-x-auto whitespace-nowrap scrollbar-none", + ), + class_name="flex lg:hidden w-full h-auto !overflow-hidden flex flex-col border border-input/90 text-sm text-card-foreground dark isolate rounded-2xl bg-card/90", + ) + + +def sidebar_desktop() -> rx.Component: + return rx.el.aside( + rx.el.div(menu_panel(), class_name="p-3 sticky top-0"), + rx.el.div( + rx.el.div(style_panel(), class_name="flex flex-col gap-y-3 p-3"), + rx.el.div( + base_color_panel(), + theme_panel(), + chart_color_panel(), + class_name="flex flex-col gap-y-3 p-3", + ), + rx.el.div(font_panel(), class_name="flex flex-col gap-y-3 p-3"), + rx.el.div(radius_panel(), class_name="flex flex-col gap-y-3 p-3"), + rx.el.div(component_panel(), class_name="flex flex-col gap-y-3 p-3"), + class_name="flex-1 min-h-0 overflow-y-auto scrollbar-none divide-y divide-input", + ), + rx.el.div( + preset_copy_button(), + open_preset_menu(), + shuffle_button(), + class_name="p-3 flex-shrink-0 flex flex-col gap-y-3 bg-secondary/50", + ), + rx.el.div(get_code_menu(), class_name="p-3 flex-shrink-0 bg-secondary/50"), + class_name="hidden lg:flex w-full max-w-[12rem] shrink-0 !overflow-hidden flex-col border border-input/90 divide-y divide-input h-full text-sm text-card-foreground dark isolate rounded-2xl bg-card/90", + ) + + +def sidebar() -> rx.Component: + return rx.el.div( + base_theme_color, + chart_color, + copy_preset_value, + darkmode, + seed, + selected_base_color_cs, + selected_chart_cs, + selected_font_cs, + selected_radius_cs, + selected_style_cs, + selected_theme_cs, + theme, + theme_color, + theme_export_method, + theme_preset_option, + css_output, + sidebar_mobile(), + sidebar_desktop(), + class_name="w-full flex-initial h-auto min-h-0 min-w-0 max-w-full lg:flex-1 lg:h-full lg:w-[12rem] lg:max-w-[12rem] lg:shrink-0", + ) diff --git a/app/templates/toc.py b/app/templates/toc.py new file mode 100644 index 00000000..7be4b112 --- /dev/null +++ b/app/templates/toc.py @@ -0,0 +1,198 @@ +from typing import Dict, List + +import reflex as rx + +from app.www.wrapper import generate_component_id +from components.icons.hugeicon import hi +from components.ui.tooltip import tooltip + + +def create_copy_button(url: str) -> rx.Component: + uid = generate_component_id() + btn_id = f"btn-{uid}" + icon_id = f"icon-{uid}" + copy_icon_svg = '' + tick_icon_svg = '' + + return tooltip.provider( + tooltip.root( + tooltip.trigger( + render_=hi( + "Copy01Icon", id=icon_id, class_name="size-4 cursor-pointer" + ), + id=btn_id, + on_click=rx.call_script( + f""" + (async () => {{ + const res = await fetch("/{url}.md"); + const text = await res.text(); + await navigator.clipboard.writeText(text); + }})(); + + const icon = document.getElementById('{icon_id}'); + + // Swap to tick + icon.innerHTML = `{tick_icon_svg}`; + + // Revert after 1.5s + setTimeout(() => {{ + icon.innerHTML = `{copy_icon_svg}`; + }}, 1500); + """ + ), + ), + tooltip.portal( + tooltip.positioner( + tooltip.popup( + rx.el.p("Copy Page", class_name="!text-xs"), + class_name="rounded-radius p-2", + ), + side="bottom", + side_offset=8, + ), + ), + ), + delay=0, + ) + + +def _create_markdown_toc_links(toc_data: List[Dict]) -> rx.Component: + """Create markdown TOC links.""" + if not toc_data: + return rx.el.div() + + return rx.el.div( + *[ + rx.el.a( + entry["text"], + href=f"#{entry['id']}", + class_name=f"cursor-pointer text-sm font-[450] hover:text-foreground no-underline{' pl-4' if entry['level'] > 1 else ''}", + ) + for entry in toc_data + ], + class_name="flex flex-col w-full gap-y-2", + ) + + +def _create_external_tool_links(url: str): + """Create links for viewing documentation in external tools.""" + + fmt_url = "https://buridan-ui.reflex.run/" + url + prompt = f"""I'm looking at this buridan/ui documentation: {fmt_url}. + Help me understand how to use it. Be ready to explain concepts, give examples, or help debug based on it. + """ + + def external_tool_item( + icon_light, + icon_dark, + tooltip_content: str, + href: str, + icon_size: str = "size-4", + ): + return tooltip.provider( + tooltip.root( + tooltip.trigger( + render_=rx.el.a( + rx.el.image( + rx.color_mode_cond(icon_light, icon_dark), + class_name=icon_size, + ), + href=href, + target="_blank", + rel="noopener noreferrer", + ) + ), + tooltip.portal( + tooltip.positioner( + tooltip.popup( + rx.el.p(tooltip_content, class_name="!text-xs"), + class_name="rounded-radius p-2", + ), + side="bottom", + side_offset=8, + ), + ), + ), + delay=0, + ) + + return rx.el.div( + external_tool_item( + icon_light="/svg/markdown/md_light.svg", + icon_dark="/svg/markdown/md_dark.svg", + tooltip_content="View Markdown", + href=f"/{url}.md", + icon_size="size-5", + ), + external_tool_item( + icon_light="/svg/openai/ai_light.svg", + icon_dark="/svg/openai/ai_dark.svg", + tooltip_content="Open in ChatGPT", + href=f"https://chatgpt.com/?q={prompt}", + ), + external_tool_item( + icon_light="/svg/claude/claude_light.svg", + icon_dark="/svg/claude/claude_dark.svg", + tooltip_content="Open in Claude", + href=f"https://claude.ai/new?q={prompt}", + ), + external_tool_item( + icon_light="/svg/reflex/reflex_light.svg", + icon_dark="/svg/reflex/reflex_dark.svg", + tooltip_content="Open in Reflex", + href=f"https://build.reflex.dev/?prompt={prompt}", + ), + rx.el.p("︲", class_name="text-muted-foreground/50 font-thin hidden lg:flex"), + create_copy_button(url), + class_name="flex flex-row items-center w-full gap-x-2.5", + ) + + +def table_of_content(url: str, toc_data: List[Dict]): + """ + Render table of contents. + + Args: + toc_data: List of dicts with 'text', 'id', 'level' keys + """ + return rx.el.div( + rx.el.div( + rx.el.div( + # + rx.el.div( + rx.el.p( + "External Tools", + class_name="text-xs text-muted-foreground font-medium pb-2", + ), + _create_external_tool_links(url), + class_name="w-full flex flex-col", + ), + # + rx.el.div( + rx.el.p( + "Agent Resources", + class_name="text-xs text-muted-foreground font-medium pb-2", + ), + rx.el.a( + rx.el.p("llms.txt", class_name="text-sm font-[450]"), + href="/llms.txt", + target="_blank", + rel="noopener noreferrer", + ), + class_name="w-full flex flex-col", + ), + # + rx.el.div( + rx.el.p( + "On This Page", + class_name="text-xs text-muted-foreground font-medium pb-2", + ), + _create_markdown_toc_links(toc_data), + class_name="w-full flex flex-col", + ), + class_name="flex flex-col w-full h-full p-4 gap-y-6", + ), + class_name="flex flex-col gap-y-4 overflow-scroll scrollbar-none", + ), + class_name="hidden xl:block max-w-[18rem] w-full sticky top-18 h-[calc(100vh-3rem)] shrink-0", + ) diff --git a/app/templates/welcome.py b/app/templates/welcome.py new file mode 100644 index 00000000..dd1426aa --- /dev/null +++ b/app/templates/welcome.py @@ -0,0 +1,56 @@ +import reflex as rx + +from app.hooks import welcome_open +from components.ui.button import button +from components.ui.dialog import dialog + + +def welcome_dialog() -> rx.Component: + """A welcome dialog that shows on first visit.""" + + # Script to mark welcome as seen in localStorage and close state + close_script = """ + localStorage.setItem("has_seen_welcome", "true"); + if (refs['_client_state_setWelcome_open']) refs['_client_state_setWelcome_open'](false); + """ + + return dialog.root( + dialog.portal( + dialog.backdrop(class_name="backdrop-blur-[5px]"), + dialog.popup( + rx.el.div( + rx.el.image( + src="/logo.webp", + class_name="w-full h-56 object-cover rounded-t-xl", + ), + rx.el.div( + rx.el.p( + "Build your theme system for Reflex", + class_name="text-foreground text-md font-medium", + ), + rx.el.p( + "Customize everything from the ground up. Pick your font, color scheme, and more.", + class_name="text-foreground text-sm font-light", + ), + rx.el.p( + "Based on the popular shadcn/ui.", + class_name="text-foreground text-sm font-light", + ), + class_name="w-full flex flex-col gap-y-2 px-3", + ), + rx.el.div( + button( + "Get Started", + class_name="w-full", + on_click=rx.call_script(close_script), + ), + class_name="w-full pt-2 dark px-3 pb-3", + ), + class_name="flex flex-col gap-y-4 w-full", + ), + class_name="!w-full max-w-sm rounded-2xl dark bg-card p-1.5 flex flex-col overflow-hidden", + ), + ), + open=welcome_open.value, + on_open_change=rx.call_script(close_script), + ) diff --git a/src/routes.py b/app/utils/routes.py similarity index 86% rename from src/routes.py rename to app/utils/routes.py index 1f7805b4..1b1c6e6a 100644 --- a/src/routes.py +++ b/app/utils/routes.py @@ -1,8 +1,8 @@ -import os import glob +import os -import src.docs.constants as constants -from src.utils.frontmatter import parse_frontmatter +import app.www.constants as constants +from app.www.frontmatter import parse_frontmatter # --- Method: Generate doc routes/urls --- @@ -65,9 +65,9 @@ def generate_doc_routes(section_folder, base_path) -> list[dict]: CHARTS_URLS = sorted( generate_doc_routes("charts", "docs/charts/"), key=lambda x: x["title"] ) -WRAPPED_COMPONENTS_URLS = generate_doc_routes( - "wrapped_components", "docs/wrapped-components/" -) -JS_INTEGRATIONS_URLS = generate_doc_routes( - "javascript_integrations", "docs/javascript-integrations/" -) +# WRAPPED_COMPONENTS_URLS = generate_doc_routes( +# "wrapped_components", "docs/wrapped-components/" +# ) +# JS_INTEGRATIONS_URLS = generate_doc_routes( +# "javascript_integrations", "docs/javascript-integrations/" +# ) diff --git a/src/__init__.py b/app/www/__init__.py similarity index 100% rename from src/__init__.py rename to app/www/__init__.py diff --git a/app/www/anatomy.py b/app/www/anatomy.py new file mode 100644 index 00000000..80743c01 --- /dev/null +++ b/app/www/anatomy.py @@ -0,0 +1,261 @@ +# app/www/anatomy.py + +ANATOMY = { + "accordion": """accordion.root( + accordion.item( + accordion.header( + accordion.trigger(), + ), + accordion.panel(), + ), + accordion.item( + accordion.header( + accordion.trigger(), + ), + accordion.panel(), + ), +)""", + "avatar": """avatar.root( + avatar.image(), + avatar.fallback(), +)""", + "badge": """badge()""", + "breadcrumb": """breadcrumb( + breadcrumb_list( + breadcrumb_item( + breadcrumb_link(), + ), + breadcrumb_separator(), + breadcrumb_item( + breadcrumb_page(), + ), + ), +)""", + "button": """button()""", + "card": """card.root( + card.header( + card.title(), + card.description(), + ), + card.content(), + card.footer(), +)""", + "checkbox": """checkbox.root( + checkbox.indicator(), +)""", + "collapsible": """collapsible.root( + collapsible.trigger(), + collapsible.panel(), +)""", + "context_menu": """context_menu.root( + context_menu.trigger(), + context_menu.portal( + context_menu.positioner( + context_menu.popup( + context_menu.item(), + context_menu.separator(), + context_menu.group( + context_menu.group_label(), + context_menu.item(), + ), + context_menu.checkbox_item( + context_menu.checkbox_item_indicator(), + ), + context_menu.radio_group( + context_menu.radio_item( + context_menu.radio_item_indicator(), + ), + ), + context_menu.submenu_root( + context_menu.submenu_trigger(), + context_menu.portal( + context_menu.positioner( + context_menu.popup(), + ), + ), + ), + ), + ), + ), +)""", + "dialog": """dialog.root( + dialog.trigger(), + dialog.portal( + dialog.backdrop(), + dialog.popup( + dialog.title(), + dialog.description(), + dialog.close(), + ), + ), +)""", + "input": """input()""", + "input_group": """# Input with addons +input_with_addons( + prefix=..., + suffix=..., +) + +# Textarea with footer +textarea_with_footer( + footer_text=..., +)""", + "kbd": """kbd() +# or +kbd_group( + kbd(), + kbd(), +)""", + "link": """link()""", + "menu": """menu.root( + menu.trigger(), + menu.portal( + menu.positioner( + menu.popup( + menu.item(), + menu.separator(), + menu.group( + menu.group_label(), + menu.item(), + ), + menu.checkbox_item( + menu.checkbox_item_indicator(), + ), + menu.radio_group( + menu.radio_item( + menu.radio_item_indicator(), + ), + ), + menu.submenu_root( + menu.submenu_trigger(), + menu.portal( + menu.positioner( + menu.popup(), + ), + ), + ), + ), + ), + ), +)""", + "metric": """metric( + label=..., + value=..., + trend=..., +)""", + "popover": """popover.root( + popover.trigger(), + popover.portal( + popover.backdrop(), + popover.positioner( + popover.popup( + popover.header( + popover.title(), + popover.description(), + ), + popover.close(), + ), + ), + ), +)""", + "scroll_area": """scroll_area.root( + scroll_area.viewport( + scroll_area.content(), + ), + scroll_area.scrollbar( + scroll_area.thumb(), + ), + scroll_area.corner(), +)""", + "select": """select.root( + select.trigger( + select.value(), + select.icon(), + ), + select.portal( + select.positioner( + select.popup( + select.group( + select.group_label(), + select.item( + select.item_text(), + select.item_indicator(), + ), + ), + select.separator(), + ), + ), + ), +)""", + "skeleton": """skeleton_component()""", + "slider": """slider.root( + slider.control( + slider.track( + slider.indicator(), + slider.thumb(), + ), + ), +)""", + "switch": """switch.root( + switch.thumb(), +)""", + "table": """table.root( + table.header( + table.row( + table.head(), + ), + ), + table.body( + table.row( + table.cell(), + ), + ), + table.footer(), + table.caption(), +)""", + "tabs": """tabs.root( + tabs.list( + tabs.tab(), + tabs.indicator(), + ), + tabs.panel(), +)""", + "textarea": """textarea()""", + "theme_switcher": """theme_switcher()""", + "toggle": """toggle()""", + "toggle_group": """toggle_group( + toggle(), + toggle(), +)""", + "tooltip": """tooltip.root( + tooltip.trigger(), + tooltip.portal( + tooltip.positioner( + tooltip.popup( + tooltip.arrow(), + content=..., + ), + ), + ), +)""", + "typography": """typography_h1() +typography_h2() +typography_h3() +typography_h4() +typography_p() +typography_blockquote() +typography_list() +typography_inline_code() +typography_lead() +typography_large() +typography_small() +typography_muted() +typography_table( + typography_table_header( + typography_table_head(), + ), + typography_table_row( + typography_table_cell(), + ), +)""", +} diff --git a/app/www/constants.py b/app/www/constants.py new file mode 100644 index 00000000..06a6f082 --- /dev/null +++ b/app/www/constants.py @@ -0,0 +1,20 @@ +from dataclasses import dataclass +from pathlib import Path +from typing import Dict, List + +import reflex as rx + + +@dataclass +class DocDataStruct: + """The data structure for the generated doc page""" + + url: str + component: List[rx.Component] + table_of_content: List[Dict] + + +# --- Docs Path Constants --- +DOCS_BASE_DIR = Path("docs") +DOCS_LIBRARY_ROOT = "app/www/library" +COMPONENTS_ROOT = "components/ui" diff --git a/src/utils/frontmatter.py b/app/www/frontmatter.py similarity index 100% rename from src/utils/frontmatter.py rename to app/www/frontmatter.py diff --git a/src/docs/generator.py b/app/www/generator.py similarity index 94% rename from src/docs/generator.py rename to app/www/generator.py index fc710ece..436e5ac3 100644 --- a/src/docs/generator.py +++ b/app/www/generator.py @@ -1,14 +1,12 @@ import os import re -import src.docs.constants as constants - - from typing import List -from src.docs.parser import DocParser -from src.utils.frontmatter import parse_frontmatter +import app.www.constants as constants +from app.www.frontmatter import parse_frontmatter +from app.www.parser import DocParser -parser = DocParser(dynamic_load_dirs=[constants.DOCS_LIBRARY_ROOT]) +parser = DocParser(dynamic_load_dirs=[constants.DOCS_LIBRARY_ROOT, constants.COMPONENTS_ROOT]) def generate_docs_library() -> List[constants.DocDataStruct]: diff --git a/src/docs/__init__.py b/app/www/library/__init__.py similarity index 100% rename from src/docs/__init__.py rename to app/www/library/__init__.py diff --git a/app/www/library/charts/area/v1.py b/app/www/library/charts/area/v1.py new file mode 100644 index 00000000..396e34d8 --- /dev/null +++ b/app/www/library/charts/area/v1.py @@ -0,0 +1,68 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186}, + {"month": "Feb", "desktop": 305}, + {"month": "Mar", "desktop": 237}, + {"month": "Apr", "desktop": 73}, + {"month": "May", "desktop": 209}, + {"month": "Jun", "desktop": 214}, +] + + +def areachart_v1(): + + return card.root( + card.header( + card.title("Area Chart"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.area_chart( + chart_tooltip(label="show"), + rx.recharts.cartesian_grid( + horizontal=True, + vertical=False, + class_name="opacity-30", + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "border") + " w-full p-0", + ) diff --git a/app/www/library/charts/area/v10.py b/app/www/library/charts/area/v10.py new file mode 100644 index 00000000..1395aba4 --- /dev/null +++ b/app/www/library/charts/area/v10.py @@ -0,0 +1,108 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186, "mobile": 80}, + {"month": "Feb", "desktop": 305, "mobile": 200}, + {"month": "Mar", "desktop": 237, "mobile": 120}, + {"month": "Apr", "desktop": 73, "mobile": 190}, + {"month": "May", "desktop": 209, "mobile": 130}, + {"month": "Jun", "desktop": 214, "mobile": 140}, +] + + +def areachart_v10(): + series = [("mobile", "Mobile", "--chart-1"), ("desktop", "Desktop", "--chart-2")] + + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Area Chart - Mixed"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + rx.hstack( + rx.foreach( + series, + lambda s: rx.hstack( + rx.box(class_name="size-2 rounded-full", bg=f"var({s[2]})"), + rx.text( + s[1], + class_name="text-xs font-medium", + color=rx.color("slate", 11), + ), + align="center", + spacing="2", + ), + ), + class_name="flex items-center gap-4", + ), + align="center", + justify="between", + width="100%", + ), + ), + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="mobile", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-2)", + stroke="var(--chart-2)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + rx.recharts.y_axis( + width=30, + axis_line=False, + min_tick_gap=50, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/area/v2.py b/app/www/library/charts/area/v2.py new file mode 100644 index 00000000..9adb3e20 --- /dev/null +++ b/app/www/library/charts/area/v2.py @@ -0,0 +1,67 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186}, + {"month": "Feb", "desktop": 305}, + {"month": "Mar", "desktop": 237}, + {"month": "Apr", "desktop": 73}, + {"month": "May", "desktop": 209}, + {"month": "Jun", "desktop": 214}, +] + + +def areachart_v2(): + + return card.root( + card.header( + card.title("Area Chart - Linear"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + type_="linear", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/area/v3.py b/app/www/library/charts/area/v3.py new file mode 100644 index 00000000..b9900c01 --- /dev/null +++ b/app/www/library/charts/area/v3.py @@ -0,0 +1,67 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186}, + {"month": "Feb", "desktop": 305}, + {"month": "Mar", "desktop": 237}, + {"month": "Apr", "desktop": 73}, + {"month": "May", "desktop": 209}, + {"month": "Jun", "desktop": 214}, +] + + +def areachart_v3(): + + return card.root( + card.header( + card.title("Area Chart - Step"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + type_="step", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/area/v4.py b/app/www/library/charts/area/v4.py new file mode 100644 index 00000000..44f9cfc2 --- /dev/null +++ b/app/www/library/charts/area/v4.py @@ -0,0 +1,75 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186, "mobile": 80}, + {"month": "Feb", "desktop": 305, "mobile": 200}, + {"month": "Mar", "desktop": 237, "mobile": 120}, + {"month": "Apr", "desktop": 73, "mobile": 190}, + {"month": "May", "desktop": 209, "mobile": 130}, + {"month": "Jun", "desktop": 214, "mobile": 140}, +] + + +def areachart_v4(): + + return card.root( + card.header( + card.title("Area Chart - Stacked"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.area( + data_key="mobile", + fill="var(--chart-2)", + stroke="var(--chart-2)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/area/v5.py b/app/www/library/charts/area/v5.py new file mode 100644 index 00000000..bdd08170 --- /dev/null +++ b/app/www/library/charts/area/v5.py @@ -0,0 +1,117 @@ +import datetime +import random + +import reflex as rx +from reflex.experimental import ClientStateVar + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + + +def areachart_v5(): + + start_date = datetime.date(2024, 4, 1) + data = [ + { + "date": (start_date + datetime.timedelta(days=i)).strftime("%b %d"), + "desktop": random.randint(80, 500), + "mobile": random.randint(100, 550), + } + for i in range(91) + ] + + SelectedRange = ClientStateVar.create("area_selected", data) + + def gradient(id_: str, color: str): + return rx.el.svg.linear_gradient( + rx.el.svg.stop(stop_color=f"var(--{color})", offset="5%", stop_opacity=0.8), + rx.el.svg.stop( + stop_color=f"var(--{color})", offset="95%", stop_opacity=0.1 + ), + x1=0, + x2=0, + y1=0, + y2=1, + id=id_, + ) + + def area(data_key: str, color: str): + return rx.recharts.area( + data_key=data_key, + fill=f"url(#{data_key})", + stack_id="a", + stroke=f"var(--{color})", + animation_easing="linear", + is_animation_active=False, + active_dot={"fill": f"var(--{color})"}, + ) + + select_options = [ + ("Last 3 Months", data), + ("Last 30 Days", data[-30:]), + ("Last 7 Days", data[-7:]), + ] + + return card.root( + card.header( + rx.el.div( + rx.el.div( + card.title("Area Chart - Dynamic"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + rx.el.select( + *[ + rx.el.option(label, on_click=SelectedRange.set_value(value)) + for label, value in select_options + ], + default_value="Last 3 Months", + class_name="relative flex items-center whitespace-nowrap justify-center gap-2 py-2 rounded-lg shadow-sm px-3 bg-secondary border border-input", + ), + class_name="flex flex-row flex-wrap gap-y-4 items-center justify-between", + ), + ), + card.content( + rx.recharts.area_chart( + rx.el.svg.defs( + gradient("desktop", "chart-1"), + gradient("mobile", "chart-2"), + ), + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + area("mobile", "chart-2"), + area("desktop", "chart-1"), + rx.recharts.x_axis( + data_key="date", + axis_line=False, + min_tick_gap=32, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=SelectedRange.value, + width="100%", + height=240, + ), + class_name="flex flex-col items-center h-[240px]", + ), + card.footer( + rx.el.div( + rx.foreach( + ["Desktop", "Mobile"], + lambda device, index: rx.el.div( + rx.el.div( + class_name=f"w-3 h-3 rounded-sm bg-chart-{index + 1}" + ), + rx.el.p(device, class_name="text-sm text-foreground"), + class_name="flex flex-row items-center gap-x-2", + ), + ), + class_name="py-4 px-4 flex w-full flex justify-center gap-8", + ), + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/area/v6.py b/app/www/library/charts/area/v6.py new file mode 100644 index 00000000..5bda8fe6 --- /dev/null +++ b/app/www/library/charts/area/v6.py @@ -0,0 +1,76 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186, "mobile": 80}, + {"month": "Feb", "desktop": 305, "mobile": 200}, + {"month": "Mar", "desktop": 237, "mobile": 120}, + {"month": "Apr", "desktop": 73, "mobile": 190}, + {"month": "May", "desktop": 209, "mobile": 130}, + {"month": "Jun", "desktop": 214, "mobile": 140}, +] + + +def areachart_v6(): + + return card.root( + card.header( + card.title("Area Chart - Legend"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="mobile", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-2)", + stroke="var(--chart-2)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + rx.recharts.legend(), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/area/v7.py b/app/www/library/charts/area/v7.py new file mode 100644 index 00000000..9c6ab8d9 --- /dev/null +++ b/app/www/library/charts/area/v7.py @@ -0,0 +1,83 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186, "mobile": 80}, + {"month": "Feb", "desktop": 305, "mobile": 200}, + {"month": "Mar", "desktop": 237, "mobile": 120}, + {"month": "Apr", "desktop": 73, "mobile": 190}, + {"month": "May", "desktop": 209, "mobile": 130}, + {"month": "Jun", "desktop": 214, "mobile": 140}, +] + + +def areachart_v7(): + + return card.root( + card.header( + card.title("Area Chart - Axes"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="mobile", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-2)", + stroke="var(--chart-2)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + rx.recharts.y_axis( + width=30, + axis_line=False, + min_tick_gap=50, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/area/v8.py b/app/www/library/charts/area/v8.py new file mode 100644 index 00000000..166d50d7 --- /dev/null +++ b/app/www/library/charts/area/v8.py @@ -0,0 +1,113 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186, "mobile": 80}, + {"month": "Feb", "desktop": 305, "mobile": 200}, + {"month": "Mar", "desktop": 237, "mobile": 120}, + {"month": "Apr", "desktop": 73, "mobile": 190}, + {"month": "May", "desktop": 209, "mobile": 130}, + {"month": "Jun", "desktop": 214, "mobile": 140}, +] + + +def areachart_v8(): + series = [("desktop", "Desktop", "--chart-1"), ("mobile", "Mobile", "--chart-2")] + + def create_gradient(var_name): + return rx.el.svg.linear_gradient( + rx.el.svg.stop( + stop_color=f"var({var_name})", offset="5%", stop_opacity=0.8 + ), + rx.el.svg.stop( + stop_color=f"var({var_name})", offset="95%", stop_opacity=0.1 + ), + x1=0, + x2=0, + y1=0, + y2=1, + id=var_name.strip("-"), + ) + + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Area Chart - Gradient"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + rx.hstack( + rx.foreach( + series, + lambda s: rx.hstack( + rx.box(class_name="size-2 rounded-full", bg=f"var({s[2]})"), + rx.text( + s[1], + class_name="text-xs font-medium", + color=rx.color("slate", 11), + ), + align="center", + spacing="2", + ), + ), + class_name="flex items-center gap-4", + ), + align="center", + justify="between", + width="100%", + ), + ), + card.content( + rx.recharts.area_chart( + rx.el.svg.defs( + *(create_gradient(s[2]) for s in series), + ), + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + *( + rx.recharts.area( + data_key=s[0], + fill=f"url(#{s[2].strip('-')})", + stroke=f"var({s[2]})", + stroke_width=2, + stack_id="1", + is_animation_active=False, + ) + for s in series + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/area/v9.py b/app/www/library/charts/area/v9.py new file mode 100644 index 00000000..05475b8d --- /dev/null +++ b/app/www/library/charts/area/v9.py @@ -0,0 +1,84 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186}, + {"month": "Feb", "desktop": 305}, + {"month": "Mar", "desktop": 237}, + {"month": "Apr", "desktop": 73}, + {"month": "May", "desktop": 209}, + {"month": "Jun", "desktop": 214}, +] + + +def areachart_v9(): + + def gradient(id_: str, color: str): + return rx.el.svg.linear_gradient( + rx.el.svg.stop(stop_color=f"var(--{color})", offset="5%", stop_opacity=0.8), + rx.el.svg.stop( + stop_color=f"var(--{color})", offset="95%", stop_opacity=0.1 + ), + x1=0, + x2=0, + y1=0, + y2=1, + id=id_, + ) + + return card.root( + card.header( + card.title("Area Chart - Step with Gradient"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.area_chart( + rx.el.svg.defs( + gradient("desktop", "chart-1"), + ), + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="desktop", + fill="url(#desktop)", + stroke="var(--chart-1)", + stroke_width=2, + type_="step", + is_animation_active=False, + active_dot={"fill": "var(--chart-1)"}, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/bar/v1.py b/app/www/library/charts/bar/v1.py new file mode 100644 index 00000000..98ed5ec7 --- /dev/null +++ b/app/www/library/charts/bar/v1.py @@ -0,0 +1,72 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186, "mobile": 80}, + {"month": "Feb", "desktop": 305, "mobile": 200}, + {"month": "Mar", "desktop": 237, "mobile": 120}, + {"month": "Apr", "desktop": 73, "mobile": 190}, + {"month": "May", "desktop": 209, "mobile": 130}, + {"month": "Jun", "desktop": 214, "mobile": 140}, +] + + +def barchart_v1(): + + return card.root( + card.header( + card.title("Bar Chart - Multiple"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + data_key="desktop", + fill="var(--chart-1)", + radius=4, + is_animation_active=False, + ), + rx.recharts.bar( + data_key="mobile", + fill="var(--chart-2)", + radius=4, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + ), + class_name="h-[250px]", + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/bar/v10.py b/app/www/library/charts/bar/v10.py new file mode 100644 index 00000000..351105f5 --- /dev/null +++ b/app/www/library/charts/bar/v10.py @@ -0,0 +1,107 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +sport_data = [ + {"date": "Jan 23", "Running": 167, "Cycling": 145}, + {"date": "Feb 23", "Running": 125, "Cycling": 110}, + {"date": "Mar 23", "Running": 156, "Cycling": 149}, + {"date": "Apr 23", "Running": 165, "Cycling": 112}, + {"date": "May 23", "Running": 153, "Cycling": 138}, + {"date": "Jun 23", "Running": 124, "Cycling": 145}, + {"date": "Jul 23", "Running": 164, "Cycling": 134}, +] + +activities = ["Running", "Cycling"] +chart_colors = ["var(--chart-1)", "var(--chart-2)"] + + +def create_alternating_chart(active_key: str): + return rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + *[ + rx.recharts.bar( + is_animation_active=False, + radius=4, + data_key=key, + fill=color, + custom_attrs={"opacity": rx.cond(key == active_key, "0.25", "1")}, + ) + for key, color in zip(activities, chart_colors) + ], + rx.recharts.x_axis( + data_key="date", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=sport_data, + width="100%", + height=250, + ) + + +def barchart_v10(): + + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Sport Activities"), + card.description("Running vs Cycling load"), + class_name="flex flex-col gap-y-1.5", + ), + rx.tabs.root( + rx.tabs.list( + *[ + rx.tabs.trigger( + rx.text(activity, class_name="text-xs font-semibold"), + value=str(i + 1), + ) + for i, activity in enumerate(activities) + ] + ), + default_value="1", + ), + align="center", + justify="between", + width="100%", + ), + ), + card.content( + rx.tabs.root( + *[ + rx.tabs.content( + create_alternating_chart(active), + value=str(i + 1), + ) + for i, active in enumerate(activities) + ], + default_value="1", + width="100%", + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/bar/v2.py b/app/www/library/charts/bar/v2.py new file mode 100644 index 00000000..848592ca --- /dev/null +++ b/app/www/library/charts/bar/v2.py @@ -0,0 +1,64 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186}, + {"month": "Feb", "desktop": 305}, + {"month": "Mar", "desktop": 237}, + {"month": "Apr", "desktop": 73}, + {"month": "May", "desktop": 209}, + {"month": "Jun", "desktop": 214}, +] + + +def barchart_v2(): + + return card.root( + card.header( + card.title("Bar Chart - Horizontal"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.bar( + data_key="desktop", + fill="var(--chart-1)", + radius=4, + is_animation_active=False, + ), + rx.recharts.x_axis(type_="number", hide=True, tick_size=0), + rx.recharts.y_axis( + data_key="month", + type_="category", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + ), + data=data, + layout="vertical", + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/bar/v3.py b/app/www/library/charts/bar/v3.py new file mode 100644 index 00000000..29e89522 --- /dev/null +++ b/app/www/library/charts/bar/v3.py @@ -0,0 +1,75 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186, "mobile": 80}, + {"month": "Feb", "desktop": 305, "mobile": 200}, + {"month": "Mar", "desktop": 237, "mobile": 120}, + {"month": "Apr", "desktop": 73, "mobile": 190}, + {"month": "May", "desktop": 209, "mobile": 130}, + {"month": "Jun", "desktop": 214, "mobile": 140}, +] + + +def barchart_v3(): + + return card.root( + card.header( + card.title("Bar Chart - Legend"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + data_key="desktop", + fill="var(--chart-1)", + stack_id="a", + radius=[0, 0, 4, 4], + is_animation_active=False, + ), + rx.recharts.bar( + data_key="mobile", + fill="var(--chart-2)", + stack_id="a", + radius=[4, 4, 0, 0], + is_animation_active=False, + ), + rx.recharts.y_axis(type_="number", hide=True), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + rx.recharts.legend(), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/bar/v4.py b/app/www/library/charts/bar/v4.py new file mode 100644 index 00000000..c387f37a --- /dev/null +++ b/app/www/library/charts/bar/v4.py @@ -0,0 +1,72 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186}, + {"month": "Feb", "desktop": 340}, + {"month": "Mar", "desktop": 237}, + {"month": "Apr", "desktop": 73}, + {"month": "May", "desktop": 209}, + {"month": "Jun", "desktop": 214}, +] + + +def barchart_v4(): + + return card.root( + card.header( + card.title("Bar Chart - Labeled"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + rx.recharts.label_list( + data_key="desktop", + position="top", + offset=10, + ), + data_key="desktop", + fill="var(--chart-1)", + radius=4, + is_animation_active=False, + ), + rx.recharts.y_axis(type_="number", hide=True), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + margin={"top": 20}, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/bar/v5.py b/app/www/library/charts/bar/v5.py new file mode 100644 index 00000000..27b4b41d --- /dev/null +++ b/app/www/library/charts/bar/v5.py @@ -0,0 +1,118 @@ +from datetime import datetime + +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + + +def barchart_v5(): + from reflex.experimental import ClientStateVar + + data = [ + {"date": "2024-04-01", "desktop": 222, "mobile": 150}, + {"date": "2024-04-02", "desktop": 97, "mobile": 180}, + {"date": "2024-04-03", "desktop": 167, "mobile": 120}, + {"date": "2024-04-04", "desktop": 242, "mobile": 260}, + {"date": "2024-04-05", "desktop": 373, "mobile": 290}, + {"date": "2024-04-06", "desktop": 301, "mobile": 340}, + {"date": "2024-04-07", "desktop": 245, "mobile": 180}, + {"date": "2024-04-08", "desktop": 409, "mobile": 320}, + {"date": "2024-04-09", "desktop": 59, "mobile": 110}, + {"date": "2024-04-10", "desktop": 261, "mobile": 190}, + {"date": "2024-04-11", "desktop": 327, "mobile": 350}, + {"date": "2024-04-12", "desktop": 292, "mobile": 210}, + {"date": "2024-04-13", "desktop": 342, "mobile": 380}, + {"date": "2024-04-14", "desktop": 137, "mobile": 220}, + {"date": "2024-06-02", "desktop": 470, "mobile": 410}, + {"date": "2024-06-03", "desktop": 103, "mobile": 160}, + {"date": "2024-06-04", "desktop": 439, "mobile": 380}, + {"date": "2024-06-05", "desktop": 88, "mobile": 140}, + {"date": "2024-06-06", "desktop": 294, "mobile": 250}, + {"date": "2024-06-07", "desktop": 323, "mobile": 370}, + {"date": "2024-06-08", "desktop": 385, "mobile": 320}, + {"date": "2024-06-09", "desktop": 438, "mobile": 480}, + {"date": "2024-06-10", "desktop": 155, "mobile": 200}, + {"date": "2024-06-11", "desktop": 92, "mobile": 150}, + {"date": "2024-06-12", "desktop": 492, "mobile": 420}, + {"date": "2024-06-13", "desktop": 81, "mobile": 130}, + {"date": "2024-06-14", "desktop": 426, "mobile": 380}, + {"date": "2024-06-15", "desktop": 307, "mobile": 350}, + ] + + formatted_data = [ + { + "date": datetime.strptime(item["date"], "%Y-%m-%d").strftime("%b %d"), + "desktop": item["desktop"], + "mobile": item["mobile"], + } + for item in data + ] + + SelectedType = ClientStateVar.create("bar_selected", "mobile") + + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Bar Chart - Dynamic"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + rx.el.select( + rx.el.option("Mobile", on_click=SelectedType.set_value("mobile")), + rx.el.option("Desktop", on_click=SelectedType.set_value("desktop")), + default_value="Mobile", + bg=rx.color("gray", 2), + border=f"1px solid {rx.color('gray', 4)}", + class_name="relative flex items-center whitespace-nowrap justify-center gap-2 py-2 rounded-lg shadow-sm px-3", + ), + align="center", + justify="between", + width="100%", + ), + ), + card.content( + rx.recharts.bar_chart( + chart_tooltip("hide"), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + data_key=SelectedType.value, + fill="var(--chart-1)", + radius=[2, 2, 0, 0], + is_animation_active=False, + ), + rx.recharts.y_axis(type_="number", hide=True), + rx.recharts.x_axis( + data_key="date", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=formatted_data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/bar/v6.py b/app/www/library/charts/bar/v6.py new file mode 100644 index 00000000..5c0808d2 --- /dev/null +++ b/app/www/library/charts/bar/v6.py @@ -0,0 +1,77 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186}, + {"month": "Feb", "desktop": 340}, + {"month": "Mar", "desktop": 237, "active": True}, + {"month": "Apr", "desktop": 73}, + {"month": "May", "desktop": 209}, + {"month": "Jun", "desktop": 214}, +] + +modified_data = [ + { + **item, + "stroke": ("var(--chart-3)" if item.get("active", False) else "none"), + } + for item in data +] + + +def barchart_v6(): + + return card.root( + card.header( + card.title("Bar Chart - Active"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + data_key="desktop", + fill="var(--chart-1)", + stack_id="a", + radius=4, + stroke="stroke", + stroke_width=2, + is_animation_active=False, + ), + rx.recharts.y_axis(type_="number", hide=True), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=modified_data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/bar/v7.py b/app/www/library/charts/bar/v7.py new file mode 100644 index 00000000..d0cd167b --- /dev/null +++ b/app/www/library/charts/bar/v7.py @@ -0,0 +1,63 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"browser": "Chrome", "visitors": 275, "fill": "var(--chart-1)"}, + {"browser": "Safari", "visitors": 200, "fill": "var(--chart-2)"}, + {"browser": "Firefox", "visitors": 187, "fill": "var(--chart-3)"}, + {"browser": "Edge", "visitors": 173, "fill": "var(--chart-4)"}, + {"browser": "Other", "visitors": 90, "fill": "var(--chart-5)"}, +] + + +def barchart_v7(): + + return card.root( + card.header( + card.title("Bar Chart - Mixed"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.bar( + data_key="visitors", + fill="fill", + radius=4, + is_animation_active=False, + ), + rx.recharts.x_axis(type_="number", hide=True, tick_size=0), + rx.recharts.y_axis( + data_key="browser", + type_="category", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + ), + data=data, + layout="vertical", + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/bar/v8.py b/app/www/library/charts/bar/v8.py new file mode 100644 index 00000000..10afc3f1 --- /dev/null +++ b/app/www/library/charts/bar/v8.py @@ -0,0 +1,134 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +categories = ["Successful", "Refunded"] +EUROPE = [ + {"date": f"{month} 23", "Successful": successful, "Refunded": refunded} + for month, successful, refunded in zip( + [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], + [12, 24, 48, 24, 34, 26, 12, 38, 23, 20, 24, 21], + [0, 1, 4, 2, 0, 0, 0, 2, 1, 0, 0, 8], + ) +] + +ASIA = [ + {"date": f"{month} 23", "Successful": successful, "Refunded": refunded} + for month, successful, refunded in zip( + [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], + [31, 32, 44, 23, 35, 48, 33, 38, 41, 39, 32, 19], + [1, 2, 3, 2, 1, 1, 1, 3, 2, 1, 1, 5], + ) +] + + +def create_chart(data: list[dict[str, str | int]]): + return rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.foreach( + categories, + lambda key, index: rx.recharts.bar( + data_key=key, + fill=f"var(--chart-{index + 1})", + stack_id="_", + is_animation_active=False, + ), + ), + rx.recharts.x_axis( + data_key="date", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval=2, + ), + data=data, + width="100%", + height=250, + ) + + +def barchart_v8(): + + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Online Transactions"), + card.description("Global revenue distributions"), + class_name="flex flex-col gap-y-1.5", + ), + rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger( + rx.text("Europe", class_name="text-xs font-semibold"), + value="1", + ), + rx.tabs.trigger( + rx.text("Asia", class_name="text-xs font-semibold"), + value="2", + ), + ), + default_value="1", + id="transactions-tabs", + ), + align="center", + justify="between", + width="100%", + ), + ), + card.content( + rx.tabs.root( + rx.tabs.content(create_chart(EUROPE), value="1"), + rx.tabs.content(create_chart(ASIA), value="2"), + default_value="1", + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/bar/v9.py b/app/www/library/charts/bar/v9.py new file mode 100644 index 00000000..e6d9f412 --- /dev/null +++ b/app/www/library/charts/bar/v9.py @@ -0,0 +1,93 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186, "mobile": 80, "tablet": 50}, + {"month": "Feb", "desktop": 305, "mobile": 200, "tablet": 120}, + {"month": "Mar", "desktop": 237, "mobile": 120, "tablet": 70}, + {"month": "Apr", "desktop": 73, "mobile": 190, "tablet": 30}, + {"month": "May", "desktop": 209, "mobile": 130, "tablet": 80}, +] + + +def barchart_v9(): + + return card.root( + card.header( + rx.el.div( + card.title("Bar Chart - Multiple"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + ), + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + data_key="desktop", + fill="var(--chart-1)", + radius=4, + is_animation_active=False, + ), + rx.recharts.bar( + data_key="mobile", + fill="var(--chart-2)", + radius=4, + is_animation_active=False, + ), + rx.recharts.bar( + data_key="tablet", + fill="var(--chart-3)", + radius=4, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=230, + ), + rx.el.div( + rx.foreach( + ["Desktop", "Mobile", "Tablet"], + lambda device, index: rx.el.div( + rx.el.div( + class_name=f"h-3 w-3 rounded-sm bg-chart-{index + 1}" + ), + rx.el.p(device, class_name="text-xs text-foreground"), + class_name="flex flex-row items-center gap-x-2", + ), + ), + class_name="flex items-center gap-4 justify-center", + ), + class_name="flex flex-col h-[250px]", + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(3, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/chart_tooltip.py b/app/www/library/charts/chart_tooltip.py new file mode 100644 index 00000000..7b7b5ef1 --- /dev/null +++ b/app/www/library/charts/chart_tooltip.py @@ -0,0 +1,111 @@ +from typing import Literal + +import reflex as rx + +Display = Literal["show", "hide"] +Swatch = Literal["square", "line", "border"] + + +def _deep_merge(base: dict, override: dict) -> dict: + result = base.copy() + for key, value in override.items(): + if key in result and isinstance(result[key], dict) and isinstance(value, dict): + result[key] = _deep_merge(result[key], value) + else: + result[key] = value + return result + + +class _ChartTooltip: + def __call__( + self, + label: Display = "show", + is_animation_active: bool = False, + separator: str = "", + cursor: bool = False, + item_style: dict = {}, + label_style: dict = {}, + content_style: dict = {}, + ) -> rx.Component: + defaults = { + "is_animation_active": is_animation_active, + "separator": separator, + "cursor": cursor, + "item_style": _deep_merge( + { + "color": "currentColor", + "display": "flex", + "paddingBottom": "0px", + "justifyContent": "space-between", + "textTransform": "capitalize", + }, + item_style, + ), + "label_style": _deep_merge( + { + "display": "none" if label == "hide" else "flex", + "fontWeight": "500", + }, + label_style, + ), + "content_style": _deep_merge( + { + "background": "var(--background)", + "borderColor": "var(--input)", + "borderRadius": "0.85rem", + "padding": "0.25rem 0.65rem", + "position": "relative", + }, + content_style, + ), + } + + return rx.recharts.graphing_tooltip(**defaults) + + +class _ChartTooltipContent: + def __call__(self, num_series: int, swatch: Swatch = "square") -> str: + base = """ + [&_.recharts-tooltip-item-name]:!text-muted-foreground + [&_.recharts-tooltip-item-separator]:!w-full + [&_.recharts-tooltip-item]:!w-[8rem] + [&_.recharts-tooltip-item]:!flex + [&_.recharts-tooltip-item]:!items-center + [&_.recharts-tooltip-item]:!gap-2 + """ + ( + """ + [&_.recharts-tooltip-label]:!border-l-3 + [&_.recharts-tooltip-label]:!border-[var(--chart-1)] + [&_.recharts-tooltip-label]:!pl-2 + [&_.recharts-tooltip-label]:!py-0 + """ + if swatch == "border" + else "" + ) + + lines = [] + for i in range(1, num_series + 1): + if swatch == "border": + lines.append(f""" + [&_.recharts-default-tooltip]:!py-2 !flex !flex-col !gap-y-0 + [&_.recharts-tooltip-item:nth-child({i})]:!border-l-3 + [&_.recharts-tooltip-item:nth-child({i})]:!border-[var(--chart-{i})] + [&_.recharts-tooltip-item:nth-child({i})]:!pl-2 + [&_.recharts-tooltip-item:nth-child({i})]:!py-0 + """) + else: + lines.append(f""" + [&_.recharts-tooltip-item:nth-child({i})]:before:!content-[''] + [&_.recharts-tooltip-item:nth-child({i})]:before:{"!w-3" if swatch == "square" else "!w-8"} + {"[&_.recharts-tooltip-item:nth-child(" + str(i) + ")]:before:!flex-shrink-0" if swatch == "square" else ""} + [&_.recharts-tooltip-item:nth-child({i})]:before:!h-3 + [&_.recharts-tooltip-item:nth-child({i})]:before:!rounded-sm + [&_.recharts-tooltip-item:nth-child({i})]:before:!bg-[var(--chart-{i})] + [&_.recharts-tooltip-item:nth-child({i})]:before:!block + """) + + return base + "\n".join(lines) + + +chart_tooltip = _ChartTooltip() +chart_tooltip_content = _ChartTooltipContent() diff --git a/app/www/library/charts/doughnut/v1.py b/app/www/library/charts/doughnut/v1.py new file mode 100644 index 00000000..63cd9802 --- /dev/null +++ b/app/www/library/charts/doughnut/v1.py @@ -0,0 +1,77 @@ +import reflex as rx + +from components.ui.card import card + +data = [ + {"browser": "chrome", "visitors": 275, "fill": "var(--chart-1)"}, + {"browser": "safari", "visitors": 200, "fill": "var(--chart-2)"}, + {"browser": "firefox", "visitors": 187, "fill": "var(--chart-3)"}, + {"browser": "edge", "visitors": 173, "fill": "var(--chart-4)"}, + {"browser": "other", "visitors": 90, "fill": "var(--chart-5)"}, +] + + +def doughnutchart_v1(): + + return card.root( + card.header( + card.title("Doughnut Chart"), + card.description("Browser distribution - Last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + [1, 2, 3, 4, 5], + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), + ), + data=data, + data_key="visitors", + name_key="browser", + inner_radius=60, + stroke_width=5, + stroke="var(--background)", + is_animation_active=False, + custom_attrs={"paddingAngle": 3, "cornerRadius": 5}, + ), + width="100%", + height=220, + ), + rx.el.div( + rx.foreach( + ["chrome", "safari", "firefox", "edge", "other"], + lambda browser, index: rx.el.div( + rx.el.div( + class_name=f"w-3 h-3 rounded-sm bg-chart-{index + 1}" + ), + rx.el.p( + browser, + class_name="text-sm font-semibold text-foreground capitalize", + ), + class_name="flex flex-row gap-x-2 items-center", + ), + ), + class_name="w-full flex flex-row flex-wrap gap-2 items-center justify-center", + ), + class_name="flex flex-col h-[250px] items-center", + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/doughnut/v2.py b/app/www/library/charts/doughnut/v2.py new file mode 100644 index 00000000..affa3927 --- /dev/null +++ b/app/www/library/charts/doughnut/v2.py @@ -0,0 +1,71 @@ +import reflex as rx + +from components.ui.card import card + +data = [ + {"browser": "chrome", "visitors": 275, "fill": "var(--chart-1)"}, + {"browser": "safari", "visitors": 200, "fill": "var(--chart-2)"}, + {"browser": "firefox", "visitors": 187, "fill": "var(--chart-3)"}, + {"browser": "edge", "visitors": 173, "fill": "var(--chart-4)"}, + {"browser": "other", "visitors": 90, "fill": "var(--chart-5)"}, +] + + +def doughnutchart_v2(): + + total_visitors = sum(item["visitors"] for item in data) + + return card.root( + card.header( + card.title("Doughnut Chart - Stacked"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.el.div( + rx.el.div( + rx.el.p( + str(total_visitors), + class_name="text-3xl font-bold text-foreground", + ), + rx.el.p( + "Visitors", + class_name="text-xs text-muted-foreground", + ), + class_name="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex flex-col items-center justify-center", + ), + rx.recharts.pie_chart( + # + rx.recharts.pie( + data=data, + data_key="visitors", + name_key="browser", + inner_radius=60, + stroke_width=5, + stroke="var(--background)", + is_animation_active=False, + ), + width="100%", + height=250, + ), + class_name="relative w-full", + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + # , + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/line/v1.py b/app/www/library/charts/line/v1.py new file mode 100644 index 00000000..bd5fb9e1 --- /dev/null +++ b/app/www/library/charts/line/v1.py @@ -0,0 +1,67 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186}, + {"month": "Feb", "desktop": 305}, + {"month": "Mar", "desktop": 237}, + {"month": "Apr", "desktop": 73}, + {"month": "May", "desktop": 209}, + {"month": "Jun", "desktop": 214}, +] + + +def linechart_v1(): + + return card.root( + card.header( + card.title("Line Chart"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key="desktop", + stroke="var(--chart-1)", + stroke_width=2, + type_="natural", + dot=False, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/line/v2.py b/app/www/library/charts/line/v2.py new file mode 100644 index 00000000..67f33a76 --- /dev/null +++ b/app/www/library/charts/line/v2.py @@ -0,0 +1,67 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186}, + {"month": "Feb", "desktop": 305}, + {"month": "Mar", "desktop": 237}, + {"month": "Apr", "desktop": 73}, + {"month": "May", "desktop": 209}, + {"month": "Jun", "desktop": 214}, +] + + +def linechart_v2(): + + return card.root( + card.header( + card.title("Line Chart - Linear"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key="desktop", + stroke="var(--chart-1)", + stroke_width=2, + type_="linear", + dot=False, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/line/v3.py b/app/www/library/charts/line/v3.py new file mode 100644 index 00000000..e5f5fe8a --- /dev/null +++ b/app/www/library/charts/line/v3.py @@ -0,0 +1,73 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186}, + {"month": "Feb", "desktop": 305}, + {"month": "Mar", "desktop": 237}, + {"month": "Apr", "desktop": 73}, + {"month": "May", "desktop": 209}, + {"month": "Jun", "desktop": 214}, +] + + +def linechart_v3(): + + return card.root( + card.header( + card.title("Line Chart - Label"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + rx.recharts.label_list( + position="top", + offset=20, + custom_attrs={"fontSize": "12px", "fontWeight": "bold"}, + ), + data_key="desktop", + stroke="var(--chart-1)", + stroke_width=2, + type_="linear", + dot=True, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + margin={"left": 20, "right": 20, "top": 25}, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/line/v4.py b/app/www/library/charts/line/v4.py new file mode 100644 index 00000000..067e4fe7 --- /dev/null +++ b/app/www/library/charts/line/v4.py @@ -0,0 +1,75 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186, "mobile": 80}, + {"month": "Feb", "desktop": 305, "mobile": 200}, + {"month": "Mar", "desktop": 237, "mobile": 120}, + {"month": "Apr", "desktop": 73, "mobile": 190}, + {"month": "May", "desktop": 209, "mobile": 130}, + {"month": "Jun", "desktop": 214, "mobile": 140}, +] + + +def linechart_v4(): + + return card.root( + card.header( + card.title("Line Chart - Multiple"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key="desktop", + stroke="var(--chart-1)", + stroke_width=2, + type_="natural", + dot=False, + is_animation_active=False, + ), + rx.recharts.line( + data_key="mobile", + stroke="var(--chart-2)", + stroke_width=2, + type_="natural", + dot=False, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/line/v5.py b/app/www/library/charts/line/v5.py new file mode 100644 index 00000000..675e4166 --- /dev/null +++ b/app/www/library/charts/line/v5.py @@ -0,0 +1,66 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"browser": "chrome", "visitors": 275}, + {"browser": "safari", "visitors": 200}, + {"browser": "firefox", "visitors": 187}, + {"browser": "edge", "visitors": 173}, + {"browser": "other", "visitors": 90}, +] + + +def linechart_v5(): + + return card.root( + card.header( + card.title("Line Chart - Title Label"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.line_chart( + chart_tooltip("hide"), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + rx.recharts.label_list( + position="top", + offset=20, + custom_attrs={"fontSize": "12px", "fontWeight": "bold"}, + data_key="browser", + ), + data_key="visitors", + stroke="var(--chart-1)", + stroke_width=2, + type_="natural", + dot=True, + is_animation_active=False, + active_dot={"fill": "var(--chart-1)"}, + ), + data=data, + width="100%", + height=250, + margin={"left": 25, "right": 20, "top": 25}, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/line/v6.py b/app/www/library/charts/line/v6.py new file mode 100644 index 00000000..69f70100 --- /dev/null +++ b/app/www/library/charts/line/v6.py @@ -0,0 +1,59 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"browser": "chrome", "visitors": 275}, + {"browser": "safari", "visitors": 200}, + {"browser": "firefox", "visitors": 187}, + {"browser": "edge", "visitors": 173}, + {"browser": "other", "visitors": 90}, +] + + +def linechart_v6(): + + return card.root( + card.header( + card.title("Line Chart - Minimal"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key="visitors", + type_="natural", + dot=False, + stroke="var(--chart-1)", + stroke_width=2, + is_animation_active=False, + ), + data=data, + width="100%", + height=250, + margin={"left": 20, "right": 20, "top": 25}, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/line/v7.py b/app/www/library/charts/line/v7.py new file mode 100644 index 00000000..adb35ae1 --- /dev/null +++ b/app/www/library/charts/line/v7.py @@ -0,0 +1,128 @@ +from datetime import datetime + +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + + +def linechart_v7(): + from reflex.experimental import ClientStateVar + + data = [ + {"date": "2024-04-01", "desktop": 222, "mobile": 150}, + {"date": "2024-04-02", "desktop": 97, "mobile": 180}, + {"date": "2024-04-03", "desktop": 167, "mobile": 120}, + {"date": "2024-04-04", "desktop": 242, "mobile": 260}, + {"date": "2024-04-05", "desktop": 373, "mobile": 290}, + {"date": "2024-04-06", "desktop": 301, "mobile": 340}, + {"date": "2024-04-07", "desktop": 245, "mobile": 180}, + {"date": "2024-04-08", "desktop": 409, "mobile": 320}, + {"date": "2024-04-09", "desktop": 59, "mobile": 110}, + {"date": "2024-04-10", "desktop": 261, "mobile": 190}, + {"date": "2024-04-11", "desktop": 327, "mobile": 350}, + {"date": "2024-04-12", "desktop": 292, "mobile": 210}, + {"date": "2024-04-13", "desktop": 342, "mobile": 380}, + {"date": "2024-04-14", "desktop": 137, "mobile": 220}, + {"date": "2024-05-31", "desktop": 178, "mobile": 230}, + {"date": "2024-06-01", "desktop": 178, "mobile": 200}, + {"date": "2024-06-02", "desktop": 470, "mobile": 410}, + {"date": "2024-06-03", "desktop": 103, "mobile": 160}, + {"date": "2024-06-04", "desktop": 439, "mobile": 380}, + {"date": "2024-06-05", "desktop": 88, "mobile": 140}, + {"date": "2024-06-06", "desktop": 294, "mobile": 250}, + {"date": "2024-06-07", "desktop": 323, "mobile": 370}, + {"date": "2024-06-08", "desktop": 385, "mobile": 320}, + {"date": "2024-06-09", "desktop": 438, "mobile": 480}, + {"date": "2024-06-10", "desktop": 155, "mobile": 200}, + {"date": "2024-06-11", "desktop": 92, "mobile": 150}, + {"date": "2024-06-12", "desktop": 492, "mobile": 420}, + {"date": "2024-06-13", "desktop": 81, "mobile": 130}, + {"date": "2024-06-14", "desktop": 426, "mobile": 380}, + {"date": "2024-06-15", "desktop": 307, "mobile": 350}, + {"date": "2024-06-16", "desktop": 371, "mobile": 310}, + ] + + formatted_data = [ + { + "date": datetime.strptime(item["date"], "%Y-%m-%d").strftime("%b %d"), + "desktop": item["desktop"], + "mobile": item["mobile"], + } + for item in data + ] + + SelectedType = ClientStateVar.create("selected_line", "mobile") + + return card.root( + card.header( + rx.el.div( + rx.el.div( + card.title("Line Chart - Dynamic"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + rx.el.div( + rx.el.select( + rx.el.option( + "Mobile", on_click=SelectedType.set_value("mobile") + ), + rx.el.option( + "Desktop", on_click=SelectedType.set_value("desktop") + ), + default_value="Mobile", + class_name="relative flex items-center whitespace-nowrap justify-center gap-2 py-2 rounded-lg shadow-sm px-3 bg-secondary border border-input", + ), + class_name="flex flex-row items-center gap-x-2", + ), + class_name="w-full flex flex-row flex-wrap items-center justify-between gap-y-4", + ), + ), + card.content( + rx.recharts.line_chart( + chart_tooltip("hide"), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key=SelectedType.value, + stroke="var(--chart-1)", + stroke_width=2, + type_="natural", + is_animation_active=False, + dot=False, + active_dot={"fill": "var(--chart-1)"}, + ), + rx.recharts.y_axis(type_="number", hide=True), + rx.recharts.x_axis( + data_key="date", + axis_line=False, + min_tick_gap=32, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=formatted_data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "line") + " w-full p-0", + ) diff --git a/app/www/library/charts/line/v8.py b/app/www/library/charts/line/v8.py new file mode 100644 index 00000000..37a139fe --- /dev/null +++ b/app/www/library/charts/line/v8.py @@ -0,0 +1,99 @@ +import reflex as rx + +from components.charts.chart_tooltip import chart_tooltip, chart_tooltip_content +from components.ui.card import card + +data = [ + {"month": "Jan", "desktop": 186, "mobile": 80}, + {"month": "Feb", "desktop": 305, "mobile": 200}, + {"month": "Mar", "desktop": 237, "mobile": 120}, + {"month": "Apr", "desktop": 73, "mobile": 190}, + {"month": "May", "desktop": 209, "mobile": 130}, + {"month": "Jun", "desktop": 214, "mobile": 140}, +] + + +def linechart_v8(): + + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Line Chart - Multiple"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + align="center", + justify="between", + width="100%", + ), + ), + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key="desktop", + stroke="var(--chart-1)", + stroke_width=2, + type_="linear", + dot=False, + is_animation_active=False, + active_dot={"fill": "var(--chart-1)"}, + ), + rx.recharts.line( + data_key="mobile", + stroke="var(--chart-2)", + stroke_width=2, + type_="linear", + dot=False, + is_animation_active=False, + active_dot={"fill": "var(--chart-2)"}, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=210, + ), + rx.el.div( + rx.foreach( + ["Desktop", "Mobile"], + lambda device, index: rx.el.div( + rx.el.div( + class_name=f"w-3 h-3 rounded-sm bg-chart-{index + 1}" + ), + rx.el.p(device, class_name="text-sm text-foreground"), + class_name="flex flex-row items-center gap-x-2", + ), + ), + class_name="py-4 px-4 flex w-full flex justify-center gap-8", + ), + class_name="flex flex-col h-[250px]", + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", + ) diff --git a/app/www/library/charts/pie/v1.py b/app/www/library/charts/pie/v1.py new file mode 100644 index 00000000..95907965 --- /dev/null +++ b/app/www/library/charts/pie/v1.py @@ -0,0 +1,58 @@ +import reflex as rx + +from components.ui.card import card + +data = [ + {"browser": "chrome", "visitors": 275}, + {"browser": "safari", "visitors": 200}, + {"browser": "firefox", "visitors": 187}, + {"browser": "edge", "visitors": 173}, + {"browser": "other", "visitors": 90}, +] + + +def piechart_v1(): + + return card.root( + card.header( + card.title("Pie Chart"), + card.description("Browser distribution - Last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), + ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + is_animation_active=False, + ), + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/pie/v2.py b/app/www/library/charts/pie/v2.py new file mode 100644 index 00000000..7679be42 --- /dev/null +++ b/app/www/library/charts/pie/v2.py @@ -0,0 +1,59 @@ +import reflex as rx + +from components.ui.card import card + +data = [ + {"browser": "chrome", "visitors": 275}, + {"browser": "safari", "visitors": 200}, + {"browser": "firefox", "visitors": 187}, + {"browser": "edge", "visitors": 173}, + {"browser": "other", "visitors": 90}, +] + + +def piechart_v2(): + + return card.root( + card.header( + card.title("Pie Chart - Hovering Labels"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), + ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + label=True, + is_animation_active=False, + ), + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/pie/v3.py b/app/www/library/charts/pie/v3.py new file mode 100644 index 00000000..cef539de --- /dev/null +++ b/app/www/library/charts/pie/v3.py @@ -0,0 +1,64 @@ +import reflex as rx + +from components.ui.card import card + +data = [ + {"browser": "chrome", "visitors": 275}, + {"browser": "safari", "visitors": 200}, + {"browser": "firefox", "visitors": 187}, + {"browser": "edge", "visitors": 173}, + {"browser": "other", "visitors": 90}, +] + + +def piechart_v3(): + + return card.root( + card.header( + card.title("Pie Chart - Inner Labels"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.recharts.label_list( + data_key="browser", + position="inside", + fill="var(--background)", + custom_attrs={"fontSize": "12px", "fontWeight": "bold"}, + ), + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), + ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + is_animation_active=False, + ), + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/pie/v4.py b/app/www/library/charts/pie/v4.py new file mode 100644 index 00000000..17a9588f --- /dev/null +++ b/app/www/library/charts/pie/v4.py @@ -0,0 +1,59 @@ +import reflex as rx + +from components.ui.card import card + +data = [ + {"browser": "chrome", "visitors": 275}, + {"browser": "safari", "visitors": 200}, + {"browser": "firefox", "visitors": 187}, + {"browser": "edge", "visitors": 173}, + {"browser": "other", "visitors": 90}, +] + + +def piechart_v4(): + + return card.root( + card.header( + card.title("Pie Chart - Legend"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), + ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + is_animation_active=False, + ), + rx.recharts.legend(class_name="text-xs font-medium"), + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/pie/v5.py b/app/www/library/charts/pie/v5.py new file mode 100644 index 00000000..bb3cc29d --- /dev/null +++ b/app/www/library/charts/pie/v5.py @@ -0,0 +1,59 @@ +import reflex as rx + +from components.ui.card import card + +data = [ + {"browser": "chrome", "visitors": 275}, + {"browser": "safari", "visitors": 200}, + {"browser": "firefox", "visitors": 187}, + {"browser": "edge", "visitors": 173}, + {"browser": "other", "visitors": 90}, +] + + +def piechart_v5(): + + return card.root( + card.header( + card.title("Pie Chart - Doughnut"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), + ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + inner_radius=60, + is_animation_active=False, + ), + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/pie/v6.py b/app/www/library/charts/pie/v6.py new file mode 100644 index 00000000..b1f34006 --- /dev/null +++ b/app/www/library/charts/pie/v6.py @@ -0,0 +1,63 @@ +import reflex as rx + +from components.ui.card import card + +data = [ + {"browser": "chrome", "visitors": 275}, + {"browser": "safari", "visitors": 200}, + {"browser": "firefox", "visitors": 187}, + {"browser": "edge", "visitors": 173}, + {"browser": "other", "visitors": 90}, +] + + +def piechart_v6(): + + return card.root( + card.header( + card.title("Pie Chart - Active"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), + ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + inner_radius=60, + custom_attrs={ + "activeIndex": 1, + "activeShape": {"outerRadius": 110}, + }, + is_animation_active=False, + ), + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/radar/v1.py b/app/www/library/charts/radar/v1.py new file mode 100644 index 00000000..7d1f78d4 --- /dev/null +++ b/app/www/library/charts/radar/v1.py @@ -0,0 +1,58 @@ +import reflex as rx + +from components.ui.card import card + +stats_data = [ + {"category": "Farming", "score": 8}, + {"category": "Fighting", "score": 7}, + {"category": "Aggressiveness", "score": 6}, + {"category": "Map Awareness", "score": 5}, + {"category": "Objective Control", "score": 9}, + {"category": "Positioning", "score": 7}, +] + + +def radar_v1(): + + return card.root( + card.header( + card.title("Radar Chart"), + card.description("Player performance across categories"), + ), + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid(class_name="opacity-30"), + rx.recharts.polar_angle_axis( + data_key="category", + custom_attrs={"fontSize": "12px"}, + ), + rx.recharts.radar( + data_key="score", + stroke="var(--chart-1)", + fill="var(--chart-1)", + fill_opacity=0.6, + is_animation_active=False, + ), + data=stats_data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/radar/v2.py b/app/www/library/charts/radar/v2.py new file mode 100644 index 00000000..1c91627d --- /dev/null +++ b/app/www/library/charts/radar/v2.py @@ -0,0 +1,59 @@ +import reflex as rx + +from components.ui.card import card + +stats_data = [ + {"category": "Farming", "score": 8}, + {"category": "Fighting", "score": 7}, + {"category": "Aggressiveness", "score": 6}, + {"category": "Map Awareness", "score": 5}, + {"category": "Objective Control", "score": 9}, + {"category": "Positioning", "score": 7}, +] + + +def radar_v2(): + + return card.root( + card.header( + card.title("Radar Chart - Dots"), + card.description("Detailed performance metrics"), + ), + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid(class_name="opacity-30"), + rx.recharts.polar_angle_axis( + data_key="category", + custom_attrs={"fontSize": "12px"}, + ), + rx.recharts.radar( + data_key="score", + dot=True, + stroke="var(--chart-1)", + fill="var(--chart-1)", + fill_opacity=0.6, + is_animation_active=False, + ), + data=stats_data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/radar/v3.py b/app/www/library/charts/radar/v3.py new file mode 100644 index 00000000..ac9a26ef --- /dev/null +++ b/app/www/library/charts/radar/v3.py @@ -0,0 +1,65 @@ +import reflex as rx + +from components.ui.card import card + +stats_data = [ + {"category": "Farming", "score": 6, "average": 8}, + {"category": "Fighting", "score": 8, "average": 9}, + {"category": "Aggressiveness", "score": 5, "average": 8}, + {"category": "Map Awareness", "score": 9, "average": 9}, + {"category": "Objective Control", "score": 7, "average": 8}, + {"category": "Positioning", "score": 6, "average": 9}, +] + + +def radar_v3(): + + return card.root( + card.header( + card.title("Radar Chart - Stacked"), + card.description("Comparing score against average"), + ), + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid(class_name="opacity-30"), + rx.recharts.polar_angle_axis( + data_key="category", + custom_attrs={"fontSize": "12px"}, + ), + rx.recharts.radar( + data_key="average", + stroke="var(--chart-2)", + fill="var(--chart-2)", + fill_opacity=0.4, + is_animation_active=False, + ), + rx.recharts.radar( + data_key="score", + stroke="var(--chart-1)", + fill="var(--chart-1)", + fill_opacity=0.7, + is_animation_active=False, + ), + data=stats_data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/radar/v4.py b/app/www/library/charts/radar/v4.py new file mode 100644 index 00000000..6617f5bb --- /dev/null +++ b/app/www/library/charts/radar/v4.py @@ -0,0 +1,65 @@ +import reflex as rx + +from components.ui.card import card + +stats_data = [ + {"category": "Farming", "score": 8, "average": 8}, + {"category": "Fighting", "score": 7, "average": 9}, + {"category": "Aggressiveness", "score": 6, "average": 8}, + {"category": "Map Awareness", "score": 5, "average": 9}, + {"category": "Objective Control", "score": 9, "average": 8}, + {"category": "Positioning", "score": 7, "average": 9}, +] + + +def radar_v4(): + + return card.root( + card.header( + card.title("Radar Chart - Lines Only"), + card.description("Linear comparison between score and average"), + ), + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid(class_name="opacity-30"), + rx.recharts.polar_angle_axis( + data_key="category", + custom_attrs={"fontSize": "12px"}, + ), + rx.recharts.radar( + data_key="average", + stroke="var(--chart-2)", + fill="none", + stroke_width=2, + is_animation_active=False, + ), + rx.recharts.radar( + data_key="score", + stroke="var(--chart-1)", + fill="none", + stroke_width=2, + is_animation_active=False, + ), + data=stats_data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/radar/v5.py b/app/www/library/charts/radar/v5.py new file mode 100644 index 00000000..52df9940 --- /dev/null +++ b/app/www/library/charts/radar/v5.py @@ -0,0 +1,59 @@ +import reflex as rx + +from components.ui.card import card + +stats_data = [ + {"category": "Farming", "score": 8}, + {"category": "Fighting", "score": 7}, + {"category": "Aggressiveness", "score": 6}, + {"category": "Map Awareness", "score": 5}, + {"category": "Objective Control", "score": 9}, + {"category": "Positioning", "score": 7}, +] + + +def radar_v5(): + + return card.root( + card.header( + card.title("Radar Chart - Circle Grid"), + card.description("Performance visual with circular boundaries"), + ), + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid(grid_type="circle", class_name="opacity-30"), + rx.recharts.polar_angle_axis( + data_key="category", + custom_attrs={"fontSize": "12px"}, + ), + rx.recharts.radar( + data_key="score", + dot=True, + stroke="var(--chart-1)", + fill="var(--chart-1)", + fill_opacity=0.6, + is_animation_active=False, + ), + data=stats_data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/radar/v6.py b/app/www/library/charts/radar/v6.py new file mode 100644 index 00000000..857bea9b --- /dev/null +++ b/app/www/library/charts/radar/v6.py @@ -0,0 +1,62 @@ +import reflex as rx + +from components.ui.card import card + +stats_data = [ + {"category": "Farming", "score": 7}, + {"category": "Fighting", "score": 6}, + {"category": "Aggressiveness", "score": 7}, + {"category": "Map Awareness", "score": 6}, + {"category": "Objective Control", "score": 6}, + {"category": "Positioning", "score": 7}, +] + + +def radar_v6(): + + return card.root( + card.header( + card.title("Radar Chart - Filled Grid"), + card.description("Player performance across categories"), + ), + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid( + grid_type="circle", + class_name="opacity-20 fill-primary stroke-input", + ), + rx.recharts.polar_angle_axis( + data_key="category", + axis_line_type="circle", + class_name="!text-xs stroke-input", + ), + rx.recharts.radar( + data_key="score", + dot=False, + fill="white", + stroke="none", + is_animation_active=False, + ), + data=stats_data, + width="100%", + height=250, + ), + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/app/www/library/charts/scatter/v1.py b/app/www/library/charts/scatter/v1.py new file mode 100644 index 00000000..2c137e1c --- /dev/null +++ b/app/www/library/charts/scatter/v1.py @@ -0,0 +1,106 @@ +import reflex as rx + +from components.ui.card import card + +scat_data_1 = [ + {"name": "A", "x": 10, "y": 30}, + {"name": "B", "x": 20, "y": 50}, + {"name": "C", "x": 30, "y": 70}, + {"name": "D", "x": 40, "y": 20}, + {"name": "E", "x": 50, "y": 90}, + {"name": "F", "x": 60, "y": 40}, + {"name": "G", "x": 70, "y": 60}, + {"name": "H", "x": 80, "y": 100}, + {"name": "I", "x": 90, "y": 10}, + {"name": "J", "x": 100, "y": 80}, +] + +scat_data_2 = [ + {"name": "K", "x": 5, "y": 15}, + {"name": "L", "x": 15, "y": 40}, + {"name": "M", "x": 25, "y": 60}, + {"name": "N", "x": 35, "y": 10}, + {"name": "O", "x": 45, "y": 80}, + {"name": "P", "x": 55, "y": 30}, + {"name": "Q", "x": 65, "y": 50}, + {"name": "R", "x": 75, "y": 90}, + {"name": "S", "x": 85, "y": 20}, + {"name": "T", "x": 95, "y": 70}, +] + + +def scatterchart_v1(): + + return card.root( + card.header( + rx.el.div( + card.title("Scatter Chart"), + card.description("Showing multi-series distribution"), + class_name="flex flex-col gap-y-1.5", + ) + ), + card.content( + rx.recharts.scatter_chart( + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.scatter( + name="Data 1", + data=scat_data_1, + fill="var(--chart-1)", + is_animation_active=False, + ), + rx.recharts.scatter( + name="Data 2", + data=scat_data_2, + fill="var(--chart-2)", + is_animation_active=False, + ), + rx.recharts.y_axis( + data_key="y", + hide=True, + ), + rx.recharts.x_axis( + data_key="x", + type_="number", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + ), + width="100%", + height=210, + ), + rx.el.div( + rx.foreach( + ["Data 1", "Data 2"], + lambda data, index: rx.el.div( + rx.el.div( + class_name=f"h-3 w-3 rounded-sm bg-chart-{index + 1}" + ), + rx.el.div(data, class_name="text-xs text-foreground"), + class_name="flex flex-row items-center gap-x-2", + ), + ), + class_name="flex items-center gap-4", + ), + class_name="flex flex-col h-[250px] items-center justify-center", + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name="w-full p-0", + ) diff --git a/src/docs/library/examples/accordion/accordion.py b/app/www/library/examples/accordion_basic.py similarity index 95% rename from src/docs/library/examples/accordion/accordion.py rename to app/www/library/examples/accordion_basic.py index 45f02a69..918ad87f 100644 --- a/src/docs/library/examples/accordion/accordion.py +++ b/app/www/library/examples/accordion_basic.py @@ -1,11 +1,8 @@ import reflex as rx +from components.ui.accordion import accordion -from ...base_ui.components.base.accordion import accordion - - -def accordion_example(): - """Accordion with space exploration data - only one section open at a time.""" +def accordion_basic(): return rx.el.div( accordion.root( accordion.item( diff --git a/app/www/library/examples/avatar_general.py b/app/www/library/examples/avatar_general.py new file mode 100644 index 00000000..7e755b81 --- /dev/null +++ b/app/www/library/examples/avatar_general.py @@ -0,0 +1,42 @@ +import reflex as rx +from components.ui.avatar import avatar + + +def avatar_general(): + return rx.box( + avatar( + src="https://avatars.githubusercontent.com/u/84860195?v=4", + alt="@LineIndent", + fallback="CN", + ), + avatar( + src="https://avatars.githubusercontent.com/u/198465274?s=200&v=4", + alt="@buridan-ui", + fallback="BUI", + class_name="rounded-lg", + ), + rx.box( + avatar( + src="", + alt="@buridan-ui", + fallback="BU", + ), + avatar( + src="https://avatars.githubusercontent.com/u/84860195?v=4", + alt="@buridan-ui", + fallback="BUI", + ), + avatar( + src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", + alt="@reflex", + fallback="RE", + ), + class_name=( + "flex -space-x-2 " + "*:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-[var(--background)] " + "*:data-[slot=avatar]:grayscale" + ), + ), + class_name="flex flex-row flex-wrap items-center gap-12 p-8", + ) + diff --git a/app/www/library/examples/avatar_sizes.py b/app/www/library/examples/avatar_sizes.py new file mode 100644 index 00000000..c71066b2 --- /dev/null +++ b/app/www/library/examples/avatar_sizes.py @@ -0,0 +1,40 @@ +import reflex as rx +from components.ui.avatar import avatar + + +def avatar_sizes(): + """Example showing different avatar sizes""" + return rx.box( + avatar( + src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", + alt="@reflex", + fallback="RE", + class_name="size-6", + ), + avatar( + src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", + alt="@reflex", + fallback="RE", + class_name="size-8", + ), + avatar( + src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", + alt="@reflex", + fallback="RE", + class_name="size-10", + ), + avatar( + src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", + alt="@reflex", + fallback="RE", + class_name="size-12", + ), + avatar( + src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", + alt="@reflex", + fallback="RE", + class_name="size-16", + ), + class_name="flex items-center gap-4 p-8", + ) + diff --git a/app/www/library/examples/avatar_with_badge.py b/app/www/library/examples/avatar_with_badge.py new file mode 100644 index 00000000..6ad60b54 --- /dev/null +++ b/app/www/library/examples/avatar_with_badge.py @@ -0,0 +1,24 @@ +import reflex as rx +from components.ui.avatar import avatar + + +def avatar_with_badge(): + """Example showing avatar with status badge""" + return rx.box( + rx.box( + avatar( + src="https://avatars.githubusercontent.com/u/84860195?v=4", + alt="@LineIndent", + fallback="CN", + class_name="size-12", + ), + rx.box( + class_name=( + "absolute bottom-0 right-0 size-3 rounded-full " + "bg-green-500 border-2 border-[var(--background)]" + ), + ), + class_name="relative inline-block", + ), + class_name="p-8", + ) diff --git a/app/www/library/examples/badge_default.py b/app/www/library/examples/badge_default.py new file mode 100644 index 00000000..c09afbf7 --- /dev/null +++ b/app/www/library/examples/badge_default.py @@ -0,0 +1,39 @@ +import reflex as rx +from components.ui.badge import badge + + +def badge_default(): + return rx.box( + rx.box( + badge("Badge"), + badge("Secondary", variant="secondary"), + badge("Destructive", variant="destructive"), + badge("Outline", variant="outline"), + class_name="flex w-full flex-wrap gap-2", + ), + rx.box( + badge( + rx.icon(tag="badge-check"), + "Verified", + variant="secondary", + class_name="bg-blue-500 text-white dark:bg-blue-600", + ), + badge( + "8", + class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", + ), + badge( + "99", + variant="destructive", + class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", + ), + badge( + "20+", + variant="outline", + class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", + ), + class_name="flex w-full flex-wrap gap-2", + ), + class_name="flex flex-col items-center gap-2 p-8", + ) + diff --git a/app/www/library/examples/badge_notification_count.py b/app/www/library/examples/badge_notification_count.py new file mode 100644 index 00000000..48da4d3f --- /dev/null +++ b/app/www/library/examples/badge_notification_count.py @@ -0,0 +1,27 @@ +import reflex as rx +from components.ui.badge import badge + + +def badge_notification_count(): + return rx.box( + badge( + "1", + class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", + ), + badge( + "5", + variant="destructive", + class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", + ), + badge( + "10", + variant="secondary", + class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", + ), + badge( + "99+", + variant="destructive", + class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", + ), + class_name="flex items-center gap-2 p-8", + ) diff --git a/app/www/library/examples/badge_status.py b/app/www/library/examples/badge_status.py new file mode 100644 index 00000000..8848821c --- /dev/null +++ b/app/www/library/examples/badge_status.py @@ -0,0 +1,18 @@ +import reflex as rx +from components.ui.badge import badge + + +def badge_status(): + return rx.box( + badge("New", variant="default"), + badge("Popular", variant="secondary"), + badge("Sale", variant="destructive"), + badge("Draft", variant="outline"), + badge( + rx.icon(tag="star"), + "Featured", + class_name="bg-yellow-500 text-white dark:bg-yellow-600", + ), + class_name="flex flex-wrap gap-2 p-8", + ) + diff --git a/app/www/library/examples/badge_with_icons.py b/app/www/library/examples/badge_with_icons.py new file mode 100644 index 00000000..41a1cc1b --- /dev/null +++ b/app/www/library/examples/badge_with_icons.py @@ -0,0 +1,32 @@ +import reflex as rx +from components.ui.badge import badge + + +def badge_with_icons(): + return rx.box( + badge( + rx.icon(tag="check"), + "Success", + variant="secondary", + class_name="bg-green-500 text-white dark:bg-green-600", + ), + badge( + rx.icon(tag="x"), + "Error", + variant="destructive", + ), + badge( + rx.icon(tag="triangle-alert"), + "Warning", + variant="secondary", + class_name="bg-yellow-500 text-white dark:bg-yellow-600", + ), + badge( + rx.icon(tag="info"), + "Info", + variant="secondary", + class_name="bg-blue-500 text-white dark:bg-blue-600", + ), + class_name="flex flex-wrap gap-2 p-8", + ) + diff --git a/app/www/library/examples/breadcrumb_basic_demo.py b/app/www/library/examples/breadcrumb_basic_demo.py new file mode 100644 index 00000000..b05bca7a --- /dev/null +++ b/app/www/library/examples/breadcrumb_basic_demo.py @@ -0,0 +1,50 @@ +import reflex as rx +from components.ui.breadcrumb import ( + breadcrumb, + breadcrumb_ellipsis, + breadcrumb_item, + breadcrumb_link, + breadcrumb_list, + breadcrumb_page, + breadcrumb_separator, +) + + +def breadcrumb_basic_demo(): + return rx.el.div( + breadcrumb( + breadcrumb_list( + breadcrumb_item( + breadcrumb_link("Home", href="#"), + ), + breadcrumb_separator(), + breadcrumb_item( + rx.menu.root( + rx.menu.trigger( + rx.box( + breadcrumb_ellipsis(class_name="size-4"), + rx.el.span("Toggle menu", class_name="sr-only"), + class_name="flex items-center gap-1", + ), + ), + rx.menu.content( + rx.menu.item("Documentation"), + rx.menu.item("Themes"), + rx.menu.item("GitHub"), + class_name="min-w-[8rem]", + ), + ), + ), + breadcrumb_separator(), + breadcrumb_item( + breadcrumb_link("Components", href="#"), + ), + breadcrumb_separator(), + breadcrumb_item( + breadcrumb_page("Breadcrumb"), + ), + ), + ), + class_name="p-8", + ) + diff --git a/app/www/library/examples/breadcrumb_custom_separator.py b/app/www/library/examples/breadcrumb_custom_separator.py new file mode 100644 index 00000000..f00e90dc --- /dev/null +++ b/app/www/library/examples/breadcrumb_custom_separator.py @@ -0,0 +1,34 @@ +import reflex as rx +from components.ui.breadcrumb import ( + breadcrumb, + breadcrumb_item, + breadcrumb_link, + breadcrumb_list, + breadcrumb_page, + breadcrumb_separator, +) + + +def breadcrumb_custom_separator(): + return rx.el.div( + breadcrumb( + breadcrumb_list( + breadcrumb_item( + breadcrumb_link("Home", href="#"), + ), + breadcrumb_separator( + rx.text("/", class_name="text-[var(--muted-foreground)]") + ), + breadcrumb_item( + breadcrumb_link("Blog", href="#"), + ), + breadcrumb_separator( + rx.text("/", class_name="text-[var(--muted-foreground)]") + ), + breadcrumb_item( + breadcrumb_page("Article"), + ), + ), + ), + class_name="p-8", + ) diff --git a/app/www/library/examples/breadcrumb_icon_breadcrumb.py b/app/www/library/examples/breadcrumb_icon_breadcrumb.py new file mode 100644 index 00000000..ea36234a --- /dev/null +++ b/app/www/library/examples/breadcrumb_icon_breadcrumb.py @@ -0,0 +1,45 @@ +import reflex as rx +from components.ui.breadcrumb import ( + breadcrumb, + breadcrumb_item, + breadcrumb_link, + breadcrumb_list, + breadcrumb_page, + breadcrumb_separator, +) + + +def breadcrumb_icon_breadcrumb(): + return rx.el.div( + breadcrumb( + breadcrumb_list( + breadcrumb_item( + breadcrumb_link( + rx.icon(tag="home", size=14), + "Home", + href="#", + class_name="flex flex-row gap-x-1 items-center", + ), + ), + breadcrumb_separator(), + breadcrumb_item( + breadcrumb_link( + rx.icon(tag="folder", size=14), + "Documents", + href="#", + class_name="flex flex-row gap-x-1 items-center", + ), + ), + breadcrumb_separator(), + breadcrumb_item( + breadcrumb_page( + rx.icon(tag="file-text", size=14), + "README.md", + class_name="flex flex-row gap-x-1 items-center", + ), + ), + ), + ), + class_name="p-8", + ) + diff --git a/app/www/library/examples/breadcrumb_simple_breadcrumb.py b/app/www/library/examples/breadcrumb_simple_breadcrumb.py new file mode 100644 index 00000000..f1e0354f --- /dev/null +++ b/app/www/library/examples/breadcrumb_simple_breadcrumb.py @@ -0,0 +1,35 @@ +import reflex as rx +from components.ui.breadcrumb import ( + breadcrumb, + breadcrumb_item, + breadcrumb_link, + breadcrumb_list, + breadcrumb_page, + breadcrumb_separator, +) + + +def breadcrumb_simple_breadcrumb(): + return rx.box( + breadcrumb( + breadcrumb_list( + breadcrumb_item( + breadcrumb_link("Home", href="#"), + ), + breadcrumb_separator(), + breadcrumb_item( + breadcrumb_link("Products", href="#"), + ), + breadcrumb_separator(), + breadcrumb_item( + breadcrumb_link("Electronics", href="#"), + ), + breadcrumb_separator(), + breadcrumb_item( + breadcrumb_page("Laptop"), + ), + ), + ), + class_name="p-8", + ) + diff --git a/app/www/library/examples/button_default.py b/app/www/library/examples/button_default.py new file mode 100644 index 00000000..a701f283 --- /dev/null +++ b/app/www/library/examples/button_default.py @@ -0,0 +1,5 @@ +from components.ui.button import button + + +def button_default(): + return button("Default", variant="default", size="sm") diff --git a/app/www/library/examples/button_destructive.py b/app/www/library/examples/button_destructive.py new file mode 100644 index 00000000..c10ce35b --- /dev/null +++ b/app/www/library/examples/button_destructive.py @@ -0,0 +1,5 @@ +from components.ui.button import button + + +def button_destructive(): + return button("Destructive", variant="destructive") diff --git a/app/www/library/examples/button_ghost.py b/app/www/library/examples/button_ghost.py new file mode 100644 index 00000000..e095540a --- /dev/null +++ b/app/www/library/examples/button_ghost.py @@ -0,0 +1,5 @@ +from components.ui.button import button + + +def button_ghost(): + return button("Ghost", variant="ghost") diff --git a/app/www/library/examples/button_icon.py b/app/www/library/examples/button_icon.py new file mode 100644 index 00000000..a2d93121 --- /dev/null +++ b/app/www/library/examples/button_icon.py @@ -0,0 +1,8 @@ +from components.icons.hugeicon import hi +from components.ui.button import button + + +def button_icon(): + return button( + hi("Mail01Icon", class_name="size-4"), variant="outline", size="icon-sm" + ) diff --git a/app/www/library/examples/button_link.py b/app/www/library/examples/button_link.py new file mode 100644 index 00000000..9f268321 --- /dev/null +++ b/app/www/library/examples/button_link.py @@ -0,0 +1,5 @@ +from components.ui.button import button + + +def button_link(): + return button("Link", variant="link") diff --git a/app/www/library/examples/button_outline.py b/app/www/library/examples/button_outline.py new file mode 100644 index 00000000..6efcef7d --- /dev/null +++ b/app/www/library/examples/button_outline.py @@ -0,0 +1,5 @@ +from components.ui.button import button + + +def button_outline(): + return button("Outline", variant="outline") diff --git a/app/www/library/examples/button_secondary.py b/app/www/library/examples/button_secondary.py new file mode 100644 index 00000000..6b54bce6 --- /dev/null +++ b/app/www/library/examples/button_secondary.py @@ -0,0 +1,5 @@ +from components.ui.button import button + + +def button_secondary(): + return button("Secondary", variant="secondary", size="sm") diff --git a/app/www/library/examples/button_sizes.py b/app/www/library/examples/button_sizes.py new file mode 100644 index 00000000..3caa3411 --- /dev/null +++ b/app/www/library/examples/button_sizes.py @@ -0,0 +1,12 @@ +import reflex as rx +from components.ui.button import button + + +def button_sizes(): + return rx.el.div( + button("Small", size="sm"), + button("Default", size="default"), + button("Large", size="lg"), + class_name="flex items-center gap-3", + ) + diff --git a/src/docs/library/examples/collapsible/e1.py b/app/www/library/examples/collapsible_high_level_demo.py similarity index 73% rename from src/docs/library/examples/collapsible/e1.py rename to app/www/library/examples/collapsible_high_level_demo.py index d2204826..efec15f5 100644 --- a/src/docs/library/examples/collapsible/e1.py +++ b/app/www/library/examples/collapsible_high_level_demo.py @@ -1,10 +1,9 @@ import reflex as rx +from components.ui.button import button +from components.ui.collapsible import collapsible -from ...base_ui.components.base.collapsible import collapsible -from ...base_ui.components.base.button import button - -def collapsible_example(): +def collapsible_high_level_demo(): return collapsible( trigger=button( "Trigger", diff --git a/src/docs/library/examples/collapsible/e2.py b/app/www/library/examples/collapsible_low_level_demo.py similarity index 86% rename from src/docs/library/examples/collapsible/e2.py rename to app/www/library/examples/collapsible_low_level_demo.py index b3655939..258b0e69 100644 --- a/src/docs/library/examples/collapsible/e2.py +++ b/app/www/library/examples/collapsible_low_level_demo.py @@ -1,10 +1,9 @@ import reflex as rx +from components.ui.button import button +from components.ui.collapsible import collapsible -from ...base_ui.components.base.collapsible import collapsible -from ...base_ui.components.base.button import button - -def collapsible_demo(): +def collapsible_low_level_demo(): return collapsible.root( collapsible.trigger( button( diff --git a/src/docs/library/examples/context_menu/e1.py b/app/www/library/examples/context_menu_low_level_demo.py similarity index 98% rename from src/docs/library/examples/context_menu/e1.py rename to app/www/library/examples/context_menu_low_level_demo.py index 972178c9..a5d149a5 100644 --- a/src/docs/library/examples/context_menu/e1.py +++ b/app/www/library/examples/context_menu_low_level_demo.py @@ -1,8 +1,8 @@ import reflex as rx -from ...base_ui.components.base.context_menu import context_menu +from components.ui.context_menu import context_menu -def context_menu_demo(): +def context_menu_low_level_demo(): return context_menu.root( context_menu.trigger( "Right click here", diff --git a/app/www/library/examples/dialog_high_level.py b/app/www/library/examples/dialog_high_level.py new file mode 100644 index 00000000..505441a6 --- /dev/null +++ b/app/www/library/examples/dialog_high_level.py @@ -0,0 +1,18 @@ +import reflex as rx + +from components.ui.button import button +from components.ui.dialog import dialog + + +def dialog_high_level(): + return dialog( + trigger=button("Open Dialog", variant="outline"), + title="Are you absolutely sure?", + description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.", + content=rx.flex( + button("Cancel", variant="outline", class_name="flex-1"), + button("Continue", class_name="flex-1"), + class_name="flex gap-2 w-full", + ), + class_name="!w-full max-w-md", + ) diff --git a/src/docs/library/examples/dialog/e1.py b/app/www/library/examples/dialog_low_level.py similarity index 57% rename from src/docs/library/examples/dialog/e1.py rename to app/www/library/examples/dialog_low_level.py index 07d19d64..c2f16e84 100644 --- a/src/docs/library/examples/dialog/e1.py +++ b/app/www/library/examples/dialog_low_level.py @@ -1,35 +1,18 @@ import reflex as rx -from ...base_ui.components.base.dialog import dialog -from ...base_ui.components.base.button import button -from ...base_ui.components.base.input import input +from components.ui.button import button +from components.ui.dialog import dialog +from components.ui.input import input -def dialog_hh(): - return dialog( - trigger=button("Open Dialog", variant="outline"), - title="Are you absolutely sure?", - description="This action cannot be undone. This will permanently delete your account and remove your data from our servers.", - content=rx.flex( - button("Cancel", variant="outline", class_name="flex-1"), - button("Continue", class_name="flex-1"), - class_name="flex gap-2 w-full", - ), - ) - -def dialog_ll(): +def dialog_low_level(): return dialog.root( - # Trigger button - dialog.trigger( - render_=button("Open Dialog"), - ), - # Portal with backdrop and popup + dialog.trigger(render_=button("Open Dialog")), dialog.portal( - dialog.backdrop(), + dialog.backdrop(class_name="backdrop-blur-[5px]"), dialog.popup( - # Header section - rx.box( - rx.flex( + rx.el.div( + rx.el.div( dialog.title("Edit Profile"), dialog.close( render_=button( @@ -47,16 +30,16 @@ def dialog_ll(): class_name="flex flex-col gap-2", ), # Content section - rx.box( - rx.box( - rx.text("Name", class_name="text-sm font-medium mb-2"), + rx.el.div( + rx.el.div( + rx.el.p("Name", class_name="text-sm font-medium mb-2"), input(placeholder="Enter your name"), ), - rx.box( - rx.text("Email", class_name="text-sm font-medium mb-2"), + rx.el.div( + rx.el.p("Email", class_name="text-sm font-medium mb-2"), input(placeholder="Enter your email", type="email"), ), - rx.flex( + rx.el.div( dialog.close( render_=button( "Cancel", variant="outline", class_name="flex-1" @@ -67,6 +50,7 @@ def dialog_ll(): ), class_name="flex flex-col gap-4", ), + class_name="!w-full max-w-lg", ), ), ) diff --git a/app/www/library/examples/input_basic_demo.py b/app/www/library/examples/input_basic_demo.py new file mode 100644 index 00000000..7cecae6d --- /dev/null +++ b/app/www/library/examples/input_basic_demo.py @@ -0,0 +1,14 @@ +import reflex as rx +from components.ui.input import input + + +def input_basic_demo(): + return rx.el.div( + rx.text("Text Input", class_name="text-sm font-medium mb-2"), + input( + type="text", + placeholder="Enter your name", + ), + class_name="w-full max-w-md p-8", + ) + diff --git a/app/www/library/examples/input_custom_input.py b/app/www/library/examples/input_custom_input.py new file mode 100644 index 00000000..32e6b244 --- /dev/null +++ b/app/www/library/examples/input_custom_input.py @@ -0,0 +1,14 @@ +import reflex as rx +from components.ui.input import input + + +def input_custom_input(): + return rx.el.div( + rx.el.p("Custom Width", class_name="text-sm font-medium mb-2"), + input( + type="text", + placeholder="Max width 300px", + class_name="max-w-[300px]", + ), + class_name="w-full max-w-md p-8", + ) diff --git a/app/www/library/examples/input_disabled.py b/app/www/library/examples/input_disabled.py new file mode 100644 index 00000000..0497c3f3 --- /dev/null +++ b/app/www/library/examples/input_disabled.py @@ -0,0 +1,15 @@ +import reflex as rx +from components.ui.input import input + + +def input_disabled(): + return rx.el.div( + rx.el.p("Disabled Input", class_name="text-sm font-medium mb-2"), + input( + type="text", + placeholder="Disabled input", + disabled=True, + ), + class_name="w-full max-w-md p-8", + ) + diff --git a/app/www/library/examples/input_email.py b/app/www/library/examples/input_email.py new file mode 100644 index 00000000..f1d8e82f --- /dev/null +++ b/app/www/library/examples/input_email.py @@ -0,0 +1,14 @@ +import reflex as rx +from components.ui.input import input + + +def input_email(): + return rx.el.div( + rx.el.p("Email Input", class_name="text-sm font-medium mb-2"), + input( + type="email", + placeholder="name@example.com", + ), + class_name="w-full max-w-md p-8", + ) + diff --git a/app/www/library/examples/input_file_input.py b/app/www/library/examples/input_file_input.py new file mode 100644 index 00000000..4b2c5ec0 --- /dev/null +++ b/app/www/library/examples/input_file_input.py @@ -0,0 +1,13 @@ +import reflex as rx +from components.ui.input import input + + +def input_file_input(): + return rx.el.div( + rx.el.p("File Input", class_name="text-sm font-medium mb-2"), + input( + type="file", + ), + class_name="w-full max-w-md p-8", + ) + diff --git a/app/www/library/examples/input_group_email_input.py b/app/www/library/examples/input_group_email_input.py new file mode 100644 index 00000000..101dae12 --- /dev/null +++ b/app/www/library/examples/input_group_email_input.py @@ -0,0 +1,14 @@ +import reflex as rx +from components.ui.input import input + + +def input_group_email_input(): + return rx.el.div( + rx.el.p("Email Input", class_name="text-sm font-medium mb-2"), + input( + type="email", + placeholder="name@example.com", + ), + class_name="w-full max-w-md p-8", + ) + diff --git a/app/www/library/examples/input_group_price_input.py b/app/www/library/examples/input_group_price_input.py new file mode 100644 index 00000000..e6f38b09 --- /dev/null +++ b/app/www/library/examples/input_group_price_input.py @@ -0,0 +1,14 @@ +import reflex as rx + +from components.ui.input_group import input_with_addons + + +def input_group_price_input(): + return rx.el.div( + input_with_addons( + placeholder="0.00", + prefix="$", + suffix="USD", + ), + class_name="w-full max-w-sm mx-auto py-6", + ) diff --git a/app/www/library/examples/input_group_textarea_with_footer.py b/app/www/library/examples/input_group_textarea_with_footer.py new file mode 100644 index 00000000..0fadd352 --- /dev/null +++ b/app/www/library/examples/input_group_textarea_with_footer.py @@ -0,0 +1,13 @@ +import reflex as rx + +from components.ui.input_group import textarea_with_footer + + +def input_group_textarea_with_footer(): + return rx.el.div( + textarea_with_footer( + placeholder="Enter your message", + footer_text="120 characters left", + ), + class_name="w-full max-w-sm mx-auto py-6", + ) diff --git a/app/www/library/examples/input_group_url_input.py b/app/www/library/examples/input_group_url_input.py new file mode 100644 index 00000000..5985d9af --- /dev/null +++ b/app/www/library/examples/input_group_url_input.py @@ -0,0 +1,14 @@ +import reflex as rx + +from components.ui.input_group import input_with_addons + + +def input_group_url_input(): + return rx.el.div( + input_with_addons( + placeholder="example.com", + prefix="https://", + suffix=".com", + ), + class_name="w-full max-w-sm mx-auto py-6", + ) diff --git a/app/www/library/examples/input_password.py b/app/www/library/examples/input_password.py new file mode 100644 index 00000000..69cd1677 --- /dev/null +++ b/app/www/library/examples/input_password.py @@ -0,0 +1,14 @@ +import reflex as rx +from components.ui.input import input + + +def input_password(): + return rx.el.div( + rx.el.p("Password Input", class_name="text-sm font-medium mb-2"), + input( + type="password", + placeholder="Enter your password", + ), + class_name="w-full max-w-md p-8", + ) + diff --git a/app/www/library/examples/kbd_common_shortcuts.py b/app/www/library/examples/kbd_common_shortcuts.py new file mode 100644 index 00000000..f2577b15 --- /dev/null +++ b/app/www/library/examples/kbd_common_shortcuts.py @@ -0,0 +1,49 @@ +import reflex as rx +from components.ui.kbd import ( + kbd, + kbd_group, +) + + +def kbd_common_shortcuts(): + """Common keyboard shortcuts""" + return rx.box( + rx.box( + rx.text("Save:", class_name="text-sm font-medium mr-2"), + kbd_group( + kbd("Ctrl"), + rx.el.span("+"), + kbd("S"), + ), + class_name="flex items-center", + ), + rx.box( + rx.text("Copy:", class_name="text-sm font-medium mr-2"), + kbd_group( + kbd("Ctrl"), + rx.el.span("+"), + kbd("C"), + ), + class_name="flex items-center", + ), + rx.box( + rx.text("Paste:", class_name="text-sm font-medium mr-2"), + kbd_group( + kbd("Ctrl"), + rx.el.span("+"), + kbd("V"), + ), + class_name="flex items-center", + ), + rx.box( + rx.text("Undo:", class_name="text-sm font-medium mr-2"), + kbd_group( + kbd("Ctrl"), + rx.el.span("+"), + kbd("Z"), + ), + class_name="flex items-center", + ), + class_name="flex flex-col gap-3 p-8", + ) + diff --git a/app/www/library/examples/kbd_complex_shortcuts.py b/app/www/library/examples/kbd_complex_shortcuts.py new file mode 100644 index 00000000..532b4f48 --- /dev/null +++ b/app/www/library/examples/kbd_complex_shortcuts.py @@ -0,0 +1,41 @@ +import reflex as rx +from components.ui.kbd import ( + kbd, + kbd_group, +) + + +def kbd_complex_shortcuts(): + """Complex multi-key shortcuts""" + return rx.box( + # Three modifier keys + rx.box( + rx.text("Screenshot:", class_name="text-sm font-medium mr-2"), + kbd_group( + kbd("Ctrl"), + rx.el.span("+"), + kbd("Shift"), + rx.el.span("+"), + kbd("S"), + ), + class_name="flex items-center mb-3", + ), + # Mac command + rx.box( + rx.text("Quit:", class_name="text-sm font-medium mr-2"), + kbd_group( + kbd("⌘"), + rx.el.span("+"), + kbd("Q"), + ), + class_name="flex items-center mb-3", + ), + # Function key + rx.box( + rx.text("Full Screen:", class_name="text-sm font-medium mr-2"), + kbd("F11"), + class_name="flex items-center", + ), + class_name="p-8", + ) + diff --git a/app/www/library/examples/kbd_default.py b/app/www/library/examples/kbd_default.py new file mode 100644 index 00000000..c78b33d3 --- /dev/null +++ b/app/www/library/examples/kbd_default.py @@ -0,0 +1,29 @@ +import reflex as rx +from components.ui.kbd import ( + kbd, + kbd_group, +) + + +def kbd_default(): + """ + Example matching the shadcn KbdDemo component. + Shows keyboard shortcuts with modifier keys. + """ + return rx.box( + # Mac modifier keys + kbd_group( + kbd("⌘"), + kbd("⇧"), + kbd("⌥"), + kbd("⌃"), + ), + # Keyboard shortcut combination + kbd_group( + kbd("Ctrl"), + rx.el.span("+"), + kbd("B"), + ), + class_name="flex flex-col items-center gap-4 p-8", + ) + diff --git a/app/www/library/examples/kbd_special_keys.py b/app/www/library/examples/kbd_special_keys.py new file mode 100644 index 00000000..b0f26104 --- /dev/null +++ b/app/www/library/examples/kbd_special_keys.py @@ -0,0 +1,22 @@ +import reflex as rx +from components.ui.kbd import ( + kbd, +) + + +def kbd_special_keys(): + """Special key examples""" + return rx.box( + kbd("Enter"), + kbd("Esc"), + kbd("Tab"), + kbd("Space"), + kbd("←"), + kbd("→"), + kbd("↑"), + kbd("↓"), + kbd("Delete"), + kbd("Backspace"), + class_name="flex flex-wrap gap-2 p-8", + ) + diff --git a/app/www/library/examples/kbd_with_icons.py b/app/www/library/examples/kbd_with_icons.py new file mode 100644 index 00000000..5213971a --- /dev/null +++ b/app/www/library/examples/kbd_with_icons.py @@ -0,0 +1,27 @@ +import reflex as rx +from components.ui.kbd import ( + kbd, + kbd_group, +) + + +def kbd_with_icons(): + """Kbd with icons""" + return rx.box( + kbd_group( + kbd( + rx.icon(tag="command", size=12), + ), + rx.el.span("+"), + kbd("K"), + ), + kbd_group( + kbd( + rx.icon(tag="arrow-left", size=12), + ), + kbd( + rx.icon(tag="arrow-right", size=12), + ), + ), + class_name="flex flex-col items-center gap-4 p-8", + ) diff --git a/app/www/library/examples/link_general.py b/app/www/library/examples/link_general.py new file mode 100644 index 00000000..876689dc --- /dev/null +++ b/app/www/library/examples/link_general.py @@ -0,0 +1,8 @@ +import reflex as rx +from components.ui.link import link + + +def link_general(): + """A basic link example.""" + return link("This is a link", href="#") + diff --git a/app/www/library/examples/link_sizes.py b/app/www/library/examples/link_sizes.py new file mode 100644 index 00000000..808224c6 --- /dev/null +++ b/app/www/library/examples/link_sizes.py @@ -0,0 +1,15 @@ +import reflex as rx +from components.ui.link import link + + +def link_sizes(): + """Link examples with different sizes.""" + return rx.box( + link("X-Small Link", href="#", size="xs"), + link("Small Link", href="#", size="sm"), + link("Medium Link", href="#", size="md"), + link("Large Link", href="#", size="lg"), + link("X-Large Link", href="#", size="xl"), + class_name="flex flex-col items-start gap-4", + ) + diff --git a/app/www/library/examples/link_variants.py b/app/www/library/examples/link_variants.py new file mode 100644 index 00000000..f16a2f51 --- /dev/null +++ b/app/www/library/examples/link_variants.py @@ -0,0 +1,12 @@ +import reflex as rx +from components.ui.link import link + + +def link_variants(): + """Link examples with different variants.""" + return rx.box( + link("Primary Link", href="#", variant="primary"), + link("Secondary Link", href="#", variant="secondary"), + class_name="flex flex-col items-start gap-4", + ) + diff --git a/app/www/library/examples/link_with_icon.py b/app/www/library/examples/link_with_icon.py new file mode 100644 index 00000000..061a112f --- /dev/null +++ b/app/www/library/examples/link_with_icon.py @@ -0,0 +1,7 @@ +import reflex as rx +from components.ui.link import link + + +def link_with_icon(): + """Link example with an icon.""" + return link("Link with icon", href="#", show_icon=True) diff --git a/app/www/library/examples/menu_checkboxes.py b/app/www/library/examples/menu_checkboxes.py new file mode 100644 index 00000000..04dccb91 --- /dev/null +++ b/app/www/library/examples/menu_checkboxes.py @@ -0,0 +1,42 @@ +from reflex.experimental import ClientStateVar + +from components.ui.button import button +from components.ui.menu import menu + +show_status_bar = ClientStateVar.create("show_status_bar", True) +show_activity_bar = ClientStateVar.create("show_activity_bar", False) +show_panel = ClientStateVar.create("show_panel", False) + + +def menu_checkboxes(): + return menu.root( + menu.trigger(render_=button("Open", variant="outline")), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.group_label("Appearance"), + menu.checkbox_item( + "Status Bar", + menu.checkbox_item_indicator(), + default_checked=show_status_bar.value, + on_checked_change=show_status_bar.set_value( + ~show_status_bar.value + ), + ), + menu.checkbox_item( + "Activity Bar", + disabled=True, + ), + menu.checkbox_item( + "Panel", + menu.checkbox_item_indicator(), + default_checked=show_panel.value, + on_checked_change=show_panel.set_value, + ), + ), + class_name="w-40", + ), + ), + ), + ) diff --git a/app/www/library/examples/menu_high_level.py b/app/www/library/examples/menu_high_level.py new file mode 100644 index 00000000..af132ab6 --- /dev/null +++ b/app/www/library/examples/menu_high_level.py @@ -0,0 +1,52 @@ +from components.ui.button import button +from components.ui.menu import menu + + +def menu_high_level(): + return menu.root( + menu.trigger(render_=button("Open", variant="outline")), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.group_label("My Account"), + menu.item("Profile"), + menu.item("Billing"), + menu.item("Settings"), + ), + menu.separator(), + menu.group( + menu.item("Team"), + menu.submenu_root( + menu.submenu_trigger("Invite users"), + menu.portal( + menu.positioner( + menu.popup( + menu.item("Email"), + menu.item("Message"), + menu.separator(), + menu.item("More..."), + ), + side="right", + align="start", + align_offset=-3, + side_offset=0, + ), + ), + ), + menu.item("New Team"), + ), + menu.separator(), + menu.group( + menu.item("GitHub"), + menu.item("Support"), + menu.item("API", disabled=True), + ), + menu.separator(), + menu.group(menu.item("Log out")), + class_name="w-40", + ), + align="start", + ), + ), + ) diff --git a/app/www/library/examples/menu_submenu.py b/app/www/library/examples/menu_submenu.py new file mode 100644 index 00000000..be633099 --- /dev/null +++ b/app/www/library/examples/menu_submenu.py @@ -0,0 +1,49 @@ +from components.ui.button import button +from components.ui.menu import menu + + +def menu_submenu(): + return menu.root( + menu.trigger(render_=button("Open", variant="outline")), + menu.portal( + menu.positioner( + menu.popup( + menu.group( + menu.item("Team"), + menu.submenu_root( + menu.submenu_trigger("Invite users"), + menu.portal( + menu.positioner( + menu.popup( + menu.item("Email"), + menu.item("Message"), + menu.submenu_root( + menu.submenu_trigger("More options"), + menu.portal( + menu.positioner( + menu.popup( + menu.item("Calendly"), + menu.item("Slack"), + menu.separator(), + menu.item("Webhook"), + ), + side="right", + align="start", + ), + ), + ), + menu.separator(), + menu.item("Advanced..."), + ), + side="right", + align="start", + ), + ), + ), + menu.item("New Team"), + ), + ), + align="start", + ), + ), + ) diff --git a/app/www/library/examples/popover_align.py b/app/www/library/examples/popover_align.py new file mode 100644 index 00000000..3364de04 --- /dev/null +++ b/app/www/library/examples/popover_align.py @@ -0,0 +1,43 @@ +import reflex as rx + +from components.ui.button import button +from components.ui.popover import popover + + +def popover_aligns(): + sides = [ + "left", + "top", + "bottom", + "right", + "inline-start", + "inline-end", + ] + + return rx.el.div( + *[ + popover.root( + popover.trigger( + render_=button( + side.replace("-", " ").title(), variant="outline", size="sm" + ) + ), + popover.portal( + popover.backdrop(), + popover.positioner( + popover.popup( + popover.header( + popover.title(f"Align: {side.capitalize()}"), + popover.description( + "Set the dimensions for the layer." + ), + ), + ), + side=side, + ), + ), + ) + for side in sides + ], + class_name="w-full max-w-xs flex flex-row flex-wrap gap-2.5 items-center justify-center", + ) diff --git a/app/www/library/examples/popover_basic.py b/app/www/library/examples/popover_basic.py new file mode 100644 index 00000000..7b7318e4 --- /dev/null +++ b/app/www/library/examples/popover_basic.py @@ -0,0 +1,19 @@ +from components.ui.button import button +from components.ui.popover import popover + + +def popover_basic(): + return popover.root( + popover.trigger(render_=button("Open Popover", variant="outline")), + popover.portal( + popover.backdrop(), + popover.positioner( + popover.popup( + popover.header( + popover.title("Dimensions"), + popover.description("Set the dimensions for the layer."), + ), + ), + ), + ), + ) diff --git a/src/docs/library/examples/scroll_area/scroll_area.py b/app/www/library/examples/scroll_area_general.py similarity index 78% rename from src/docs/library/examples/scroll_area/scroll_area.py rename to app/www/library/examples/scroll_area_general.py index 4a273e06..0a7ce96d 100644 --- a/src/docs/library/examples/scroll_area/scroll_area.py +++ b/app/www/library/examples/scroll_area_general.py @@ -1,8 +1,8 @@ import reflex as rx -from ...base_ui.components.base.scroll_area import scroll_area +from components.ui.scroll_area import scroll_area -def scroll_area_example(): +def scroll_area_general(): """A basic scroll area example.""" return rx.el.div( scroll_area( diff --git a/app/www/library/examples/scroll_area_horizontal.py b/app/www/library/examples/scroll_area_horizontal.py new file mode 100644 index 00000000..0dcc5de1 --- /dev/null +++ b/app/www/library/examples/scroll_area_horizontal.py @@ -0,0 +1,53 @@ +import reflex as rx + +from components.ui.scroll_area import scroll_area + +works = [ + { + "artist": "Ornella Binni", + "art": "https://images.unsplash.com/photo-1465869185982-5a1a7522cbcb?auto=format&fit=crop&w=300&q=80", + }, + { + "artist": "Tom Byrom", + "art": "https://images.unsplash.com/photo-1548516173-3cabfa4607e9?auto=format&fit=crop&w=300&q=80", + }, + { + "artist": "Vladimir Malyavko", + "art": "https://images.unsplash.com/photo-1494337480532-3725c85fd2ab?auto=format&fit=crop&w=300&q=80", + }, +] + + +def scroll_area_horizontal(): + return scroll_area.root( + scroll_area.viewport( + scroll_area.content( + *[ + rx.el.figure( + rx.el.div( + rx.el.image( + src=work["art"], + alt=f"Photo by {work['artist']}", + class_name="aspect-[3/4] h-64 object-cover", + ), + class_name="overflow-hidden rounded-md", + ), + rx.el.figcaption( + "Photo by ", + rx.el.span( + work["artist"], + class_name="font-semibold text-foreground", + ), + class_name="pt-2 text-xs text-muted-foreground", + ), + class_name="shrink-0", + ) + for work in works + ], + class_name="flex w-max space-x-4 p-4", + ), + ), + scroll_area.scrollbar(scroll_area.thumb(), orientation="horizontal"), + scroll_area.corner(), + class_name="w-96 rounded-radius border border-input", + ) diff --git a/app/www/library/examples/select_align_items.py b/app/www/library/examples/select_align_items.py new file mode 100644 index 00000000..48d0601e --- /dev/null +++ b/app/www/library/examples/select_align_items.py @@ -0,0 +1,65 @@ +import reflex as rx +from reflex.experimental import ClientStateVar + +from components.icons.hugeicon import hi +from components.ui.select import select +from components.ui.switch import switch + +align_with_item_trigger = ClientStateVar.create("align_with_item_trigger", False) + +items = ["apple", "banana", "orange", "grape", "blueberry", "pineapple"] + + +def select_align_with_items(): + + return rx.el.div( + rx.el.div( + rx.el.div( + rx.el.p("Align Item", class_name="font-medium text-foreground"), + rx.el.p( + "Toggle to align the item with the trigger.", + class_name="text-muted-foreground", + ), + class_name="flex flex-col gap-y-2 text-sm", + ), + switch.root( + switch.thumb(), + on_checked_change=align_with_item_trigger.set_value( + ~align_with_item_trigger.value + ), + ), + class_name="flex flex-row items-start justify-between w-full", + ), + select.root( + select.trigger( + select.value(), + select.icon(hi("ArrowDown01Icon", classs_name="size-4")), + class_name="w-full flex items-center justify-between group", + ), + select.portal( + select.positioner( + select.popup( + select.group( + select.group_label("Fruit"), + *[ + select.item( + select.item_text(fruit.capitalize()), + select.item_indicator( + hi("Tick02Icon", class_name="size-4") + ), + value=fruit, + class_name="w-full !max-w-sm flex flex-row items-center justify-between", + ) + for fruit in items + ], + ), + ), + side_offset=4, + align_item_with_trigger=align_with_item_trigger.value, + ), + ), + name="example_select", + default_value="blueberry", + ), + class_name="flex flex-col gap-y-4 max-w-sm", + ) diff --git a/app/www/library/examples/select_groups.py b/app/www/library/examples/select_groups.py new file mode 100644 index 00000000..4db2c0ce --- /dev/null +++ b/app/www/library/examples/select_groups.py @@ -0,0 +1,64 @@ +from components.icons.hugeicon import hi +from components.ui.select import select + + +def select_groups(): + fruits = [ + {"label": "Apple", "value": "apple"}, + {"label": "Banana", "value": "banana"}, + {"label": "Blueberry", "value": "blueberry"}, + ] + + vegetables = [ + {"label": "Carrot", "value": "carrot"}, + {"label": "Broccoli", "value": "broccoli"}, + {"label": "Spinach", "value": "spinach"}, + ] + + return select.root( + select.trigger( + select.value(), + select.icon( + # hi("ArrowDown01Icon", classs_name="size-4"), + ), + class_name="w-full max-w-48 flex items-center justify-between", + ), + select.portal( + select.positioner( + select.popup( + select.group( + select.group_label("Fruits"), + *[ + select.item( + select.item_text(item["label"]), + select.item_indicator( + # hi("Tick02Icon", class_name="size-4") + ), + value=item["value"], + class_name="flex flex-row items-center justify-between", + ) + for item in fruits + ], + ), + select.separator(), + select.group( + select.group_label("Vegetables"), + *[ + select.item( + select.item_text(item["label"]), + select.item_indicator( + # hi("Tick02Icon", class_name="size-4") + ), + value=item["value"], + class_name="flex flex-row items-center justify-between", + ) + for item in vegetables + ], + ), + ), + ), + ), + items=[*fruits, *vegetables], + name="select_groups", + default_value="banana", + ) diff --git a/app/www/library/examples/select_scrollable.py b/app/www/library/examples/select_scrollable.py new file mode 100644 index 00000000..8641f47e --- /dev/null +++ b/app/www/library/examples/select_scrollable.py @@ -0,0 +1,87 @@ +from components.ui.select import select + + +def select_with_scroll_arrows(): + north_america = [ + {"label": "Eastern Standard Time", "value": "est"}, + {"label": "Central Standard Time", "value": "cst"}, + {"label": "Mountain Standard Time", "value": "mst"}, + {"label": "Pacific Standard Time", "value": "pst"}, + {"label": "Alaska Standard Time", "value": "akst"}, + {"label": "Hawaii Standard Time", "value": "hst"}, + ] + + europe_africa = [ + {"label": "Greenwich Mean Time", "value": "gmt"}, + {"label": "Central European Time", "value": "cet"}, + {"label": "Eastern European Time", "value": "eet"}, + {"label": "Central Africa Time", "value": "cat"}, + {"label": "East Africa Time", "value": "eat"}, + ] + + asia = [ + {"label": "Moscow Time", "value": "msk"}, + {"label": "India Standard Time", "value": "ist"}, + {"label": "China Standard Time", "value": "cst_china"}, + {"label": "Japan Standard Time", "value": "jst"}, + ] + + return select.root( + select.trigger( + select.value(), + select.icon(), + class_name="w-full max-w-64 flex items-center justify-between", + ), + select.portal( + select.positioner( + select.popup( + select.scroll_up_arrow(), + select.list( + select.group( + select.group_label("North America"), + *[ + select.item( + select.item_text(i["label"]), + select.item_indicator(), + value=i["value"], + ) + for i in north_america + ], + ), + select.group( + select.group_label("Europe & Africa"), + *[ + select.item( + select.item_text(i["label"]), + select.item_indicator(), + value=i["value"], + ) + for i in europe_africa + ], + ), + select.group( + select.group_label("Asia"), + *[ + select.item( + select.item_text(i["label"]), + select.item_indicator(), + value=i["value"], + ) + for i in asia + ], + ), + class_name="max-h-64 overflow-y-auto", + ), + select.scroll_down_arrow(), + ), + ), + ), + items=[ + {"label": "Select timezone", "value": None}, + *north_america, + *europe_africa, + *asia, + ], + name="timezone_select", + default_value="est", + ) diff --git a/app/www/library/examples/skeleton_card.py b/app/www/library/examples/skeleton_card.py new file mode 100644 index 00000000..40e67395 --- /dev/null +++ b/app/www/library/examples/skeleton_card.py @@ -0,0 +1,15 @@ +from components.ui.card import card +from components.ui.skeleton import skeleton_component + + +def skeleton_card(): + return card.root( + card.header( + skeleton_component(class_name="h-4 w-2/3 rounded-md"), + skeleton_component(class_name="h-4 w-1/2 rounded-md"), + ), + card.content( + skeleton_component(class_name="aspect-video w-full rounded-md"), + ), + class_name="w-full max-w-xs border border-input rounded-radius", + ) diff --git a/app/www/library/examples/skeleton_general.py b/app/www/library/examples/skeleton_general.py new file mode 100644 index 00000000..5279aa10 --- /dev/null +++ b/app/www/library/examples/skeleton_general.py @@ -0,0 +1,5 @@ +from components.ui.skeleton import skeleton_component + + +def skeleton_general(): + return skeleton_component(class_name="h-8 w-32 rounded-md") diff --git a/app/www/library/examples/skeleton_table.py b/app/www/library/examples/skeleton_table.py new file mode 100644 index 00000000..ebf37f79 --- /dev/null +++ b/app/www/library/examples/skeleton_table.py @@ -0,0 +1,21 @@ +import reflex as rx + +from components.ui.skeleton import skeleton_component + + +def skeleton_table(): + """Skeleton table matching shadcn SkeletonTable layout.""" + + return rx.el.div( + *[ + rx.el.div( + skeleton_component(class_name="h-4 flex-1"), + skeleton_component(class_name="h-4 w-24"), + skeleton_component(class_name="h-4 w-20"), + class_name="flex gap-4", + key=str(i), + ) + for i in range(5) + ], + class_name="flex w-full max-w-sm flex-col gap-2", + ) diff --git a/app/www/library/examples/slider_basic.py b/app/www/library/examples/slider_basic.py new file mode 100644 index 00000000..cf845607 --- /dev/null +++ b/app/www/library/examples/slider_basic.py @@ -0,0 +1,13 @@ +import reflex as rx + +from components.ui.slider import slider + + +def slider_demo(): + return rx.el.div( + slider.root( + slider.control(slider.track(slider.indicator(), slider.thumb())), + default_value=20, + ), + class_name="w-full max-w-md flex justify-center", + ) diff --git a/app/www/library/examples/slider_range.py b/app/www/library/examples/slider_range.py new file mode 100644 index 00000000..a7dfc10b --- /dev/null +++ b/app/www/library/examples/slider_range.py @@ -0,0 +1,21 @@ +import reflex as rx + +from components.ui.slider import slider + + +def slider_range(): + return rx.el.div( + slider.root( + slider.control( + slider.track( + slider.indicator(), + slider.thumb(), + slider.thumb(), + ), + ), + default_value=[25, 50], + max=100, + step=5, + ), + class_name="w-full max-w-xs mx-auto flex justify-center", + ) diff --git a/app/www/library/examples/slider_vertical.py b/app/www/library/examples/slider_vertical.py new file mode 100644 index 00000000..b2df6b29 --- /dev/null +++ b/app/www/library/examples/slider_vertical.py @@ -0,0 +1,29 @@ +import reflex as rx + +from components.ui.slider import slider + + +def slider_vertical(): + return rx.el.div( + slider.root( + slider.control( + slider.track(slider.indicator(), slider.thumb()), + ), + default_value=[50], + max=100, + step=1, + orientation="vertical", + class_name="h-40", + ), + slider.root( + slider.control( + slider.track(slider.indicator(), slider.thumb()), + ), + default_value=[25], + max=100, + step=1, + orientation="vertical", + class_name="h-40", + ), + class_name="h-full mx-auto flex w-full max-w-xs items-center justify-center gap-6", + ) diff --git a/app/www/library/examples/tabs_basic.py b/app/www/library/examples/tabs_basic.py new file mode 100644 index 00000000..a1e6f141 --- /dev/null +++ b/app/www/library/examples/tabs_basic.py @@ -0,0 +1,73 @@ +import reflex as rx + +from components.ui.card import card +from components.ui.tabs import tabs + + +def tabs_basic(): + return rx.el.div( + tabs.root( + tabs.list( + tabs.indicator(), + tabs.tab("Overview", value="overview"), + tabs.tab("Analytics", value="analytics"), + tabs.tab("Reports", value="reports"), + tabs.tab("Settings", value="settings"), + ), + tabs.panel( + card.root( + card.header( + card.title("Overview"), + card.description( + "View your key metrics and recent project activity. Track progress across all your active projects." + ), + ), + card.content("You have 12 active projects and 3 pending tasks."), + class_name="ring-1 ring-foreground/10 rounded-[1rem] dark:bg-card", + ), + value="overview", + ), + tabs.panel( + card.root( + card.header( + card.title("Analytics"), + card.description( + "Track performance and user engagement metrics. Monitor trends and identify growth opportunities." + ), + ), + card.content("Page views are up 25% compared to last month."), + class_name="ring-1 ring-foreground/10 rounded-[1rem] dark:bg-card", + ), + value="analytics", + ), + tabs.panel( + card.root( + card.header( + card.title("Reports"), + card.description( + "Generate and download your detailed reports. Export data in multiple formats for analysis." + ), + ), + card.content("You have 5 reports ready and available to export."), + class_name="ring-1 ring-foreground/10 rounded-[1rem] dark:bg-card", + ), + value="reports", + ), + tabs.panel( + card.root( + card.header( + card.title("Settings"), + card.description( + "Manage your account preferences and options. Customize your experience to fit your needs." + ), + ), + card.content("Configure notifications, security, and themes."), + class_name="ring-1 ring-foreground/10 rounded-[1rem]", + ), + value="settings", + ), + default_value="overview", + class_name="w-[400px]", + ), + class_name="flex justify-center w-full", + ) diff --git a/app/www/library/examples/tabs_disabled.py b/app/www/library/examples/tabs_disabled.py new file mode 100644 index 00000000..f95e99b0 --- /dev/null +++ b/app/www/library/examples/tabs_disabled.py @@ -0,0 +1,19 @@ +from components.ui.tabs import tabs + + +def tabs_disabled(): + return tabs.root( + tabs.list( + tabs.indicator(), + tabs.tab( + "Home", + value="home", + ), + tabs.tab( + "Disabled", + value="settings", + disabled=True, + ), + ), + default_value="home", + ) diff --git a/app/www/library/examples/tabs_vertical.py b/app/www/library/examples/tabs_vertical.py new file mode 100644 index 00000000..86d30e0f --- /dev/null +++ b/app/www/library/examples/tabs_vertical.py @@ -0,0 +1,19 @@ +import reflex as rx + +from components.ui.tabs import tabs + + +def tabs_vertical(): + return rx.el.div( + tabs.root( + tabs.list( + tabs.indicator(), + tabs.tab("Account", value="account"), + tabs.tab("Password", value="password"), + tabs.tab("Notifications", value="notifications"), + ), + default_value="account", + orientation="vertical", + ), + class_name="flex justify-center text-sm", + ) diff --git a/app/www/library/examples/textarea_basic_demo.py b/app/www/library/examples/textarea_basic_demo.py new file mode 100644 index 00000000..ff533926 --- /dev/null +++ b/app/www/library/examples/textarea_basic_demo.py @@ -0,0 +1,5 @@ +from components.ui.textarea import textarea + + +def textarea_basic_demo(): + return textarea(placeholder="Type your message here.") diff --git a/app/www/library/examples/textarea_custom_text_area.py b/app/www/library/examples/textarea_custom_text_area.py new file mode 100644 index 00000000..546a222d --- /dev/null +++ b/app/www/library/examples/textarea_custom_text_area.py @@ -0,0 +1,13 @@ +import reflex as rx +from components.ui.textarea import textarea + + +def textarea_custom_text_area(): + return rx.el.div( + rx.el.p("Custom Height", class_name="text-sm font-medium mb-2"), + textarea( + placeholder="Taller textarea", + class_name="min-h-32", + ), + class_name="w-full max-w-md p-8", + ) diff --git a/app/www/library/examples/textarea_disabled.py b/app/www/library/examples/textarea_disabled.py new file mode 100644 index 00000000..4c5c2e01 --- /dev/null +++ b/app/www/library/examples/textarea_disabled.py @@ -0,0 +1,17 @@ +from components.ui.field import field +from components.ui.textarea import textarea + + +def textarea_disabled(): + return field.root( + field.label( + "Message", + html_for="textarea-disabled", + ), + textarea( + id="textarea-disabled", + placeholder="Type your message here.", + disabled=True, + ), + **{"data-disabled": True}, + ) diff --git a/app/www/library/examples/textarea_field.py b/app/www/library/examples/textarea_field.py new file mode 100644 index 00000000..580d1ddb --- /dev/null +++ b/app/www/library/examples/textarea_field.py @@ -0,0 +1,18 @@ +from components.ui.field import field +from components.ui.textarea import textarea + + +def textarea_field(): + return field.root( + field.label( + "Message", + html_for="textarea-message", + ), + field.description( + "Enter your message below.", + ), + textarea( + id="textarea-message", + placeholder="Type your message here.", + ), + ) diff --git a/app/www/library/examples/textarea_invalid.py b/app/www/library/examples/textarea_invalid.py new file mode 100644 index 00000000..768fe591 --- /dev/null +++ b/app/www/library/examples/textarea_invalid.py @@ -0,0 +1,20 @@ +from components.ui.field import field +from components.ui.textarea import textarea + + +def textarea_invalid(): + return field.root( + field.label( + "Message", + html_for="textarea-invalid", + ), + textarea( + id="textarea-invalid", + placeholder="Type your message here.", + **{"aria-invalid": True}, + ), + field.description( + "Please enter a valid message.", + ), + **{"data-invalid": True}, + ) diff --git a/app/www/library/examples/toggle_disabled.py b/app/www/library/examples/toggle_disabled.py new file mode 100644 index 00000000..48417cd1 --- /dev/null +++ b/app/www/library/examples/toggle_disabled.py @@ -0,0 +1,6 @@ +from components.icons.hugeicon import hi +from components.ui.toggle import toggle + + +def toggle_disabled(): + return toggle(hi("TextUnderlineIcon", class_name="size-4"), disabled=True) diff --git a/app/www/library/examples/toggle_general.py b/app/www/library/examples/toggle_general.py new file mode 100644 index 00000000..1cad063f --- /dev/null +++ b/app/www/library/examples/toggle_general.py @@ -0,0 +1,22 @@ +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.toggle import toggle + + +def toggle_general(): + return rx.el.div( + toggle( + hi( + "Bookmark02Icon", + class_name="size-4", + ), + "Bookmark", + icon_variant="fill", + ), + toggle( + hi("TextUnderlineIcon", class_name="size-4"), + "Underline", + ), + class_name="flex flex-row gap-x-2 items-center justify-center", + ) diff --git a/app/www/library/examples/toggle_group_general.py b/app/www/library/examples/toggle_group_general.py new file mode 100644 index 00000000..50d0cbca --- /dev/null +++ b/app/www/library/examples/toggle_group_general.py @@ -0,0 +1,15 @@ +from components.icons.hugeicon import hi +from components.ui.toggle import toggle +from components.ui.toggle_group import toggle_group + +toggle_style = "flex size-8 items-center justify-center rounded-sm active:bg-secondary text-muted-foreground active:text-foreground" + + +def toggle_group_general(): + return toggle_group( + toggle(hi("TextBoldIcon"), value="bold", class_name=toggle_style), + toggle(hi("TextItalicIcon"), value="italic", class_name=toggle_style), + toggle(hi("TextUnderlineIcon"), value="underline", class_name=toggle_style), + default_value=["bold"], + class_name="flex gap-px rounded-md border border-input bg-background p-0.5", + ) diff --git a/app/www/library/examples/toggle_group_multiple_selection.py b/app/www/library/examples/toggle_group_multiple_selection.py new file mode 100644 index 00000000..be25936d --- /dev/null +++ b/app/www/library/examples/toggle_group_multiple_selection.py @@ -0,0 +1,16 @@ +from components.icons.hugeicon import hi +from components.ui.toggle import toggle +from components.ui.toggle_group import toggle_group + +toggle_style = "flex size-8 items-center justify-center rounded-sm active:bg-secondary text-muted-foreground active:text-foreground" + + +def toggle_group_multiple_selection(): + return toggle_group( + toggle(hi("TextAlignLeftIcon"), value="left", class_name=toggle_style), + toggle(hi("TextAlignCenterIcon"), value="center", class_name=toggle_style), + toggle(hi("TextAlignRightIcon"), value="right", class_name=toggle_style), + default_value=["left", "right"], + multiple=True, + class_name="flex gap-px rounded-md border border-input bg-background p-0.5", + ) diff --git a/app/www/library/examples/toggle_pressed_state.py b/app/www/library/examples/toggle_pressed_state.py new file mode 100644 index 00000000..33bd1214 --- /dev/null +++ b/app/www/library/examples/toggle_pressed_state.py @@ -0,0 +1,6 @@ +from components.icons.hugeicon import hi +from components.ui.toggle import toggle + + +def toggle_pressed_state(): + return toggle(hi("TextItalicIcon", class_name="size-4"), default_pressed=True) diff --git a/app/www/library/examples/tooltip_general.py b/app/www/library/examples/tooltip_general.py new file mode 100644 index 00000000..45d672a8 --- /dev/null +++ b/app/www/library/examples/tooltip_general.py @@ -0,0 +1,18 @@ +from components.ui.button import button +from components.ui.tooltip import tooltip + + +def tooltip_general(): + return tooltip.provider( + tooltip.root( + tooltip.trigger( + render_=button("Hover", variant="outline", size="sm"), + ), + tooltip.portal( + tooltip.positioner( + tooltip.popup(tooltip.arrow(), "Add to library"), + ), + ), + ), + delay=0, + ) diff --git a/app/www/library/examples/tooltip_sides.py b/app/www/library/examples/tooltip_sides.py new file mode 100644 index 00000000..f9116852 --- /dev/null +++ b/app/www/library/examples/tooltip_sides.py @@ -0,0 +1,30 @@ +import reflex as rx + +from components.ui.button import button +from components.ui.tooltip import tooltip + +sides = ["left", "top", "bottom", "right"] + + +def tooltip_sides(): + + return rx.el.div( + *[ + tooltip.provider( + tooltip.root( + tooltip.trigger( + render_=button(side.capitalize(), variant="outline", size="sm"), + ), + tooltip.portal( + tooltip.positioner( + tooltip.popup(tooltip.arrow(), "Add to library"), + side=side, + ), + ), + ), + delay=0, + ) + for side in sides + ], + class_name="flex flex-wrap gap-2", + ) diff --git a/app/www/library/examples/typography_blockquote.py b/app/www/library/examples/typography_blockquote.py new file mode 100644 index 00000000..25e9f097 --- /dev/null +++ b/app/www/library/examples/typography_blockquote.py @@ -0,0 +1,10 @@ +import reflex as rx + + +def blockquote(): + return rx.el.blockquote( + """ + "Five hundred times have the red leaves fallen in Mirkwood in my home since then," said Legolas, "and but a little while does that seem to us." + """, + class_name="mt-6 border-l-2 pl-6 italic", + ) diff --git a/app/www/library/examples/typography_h1.py b/app/www/library/examples/typography_h1.py new file mode 100644 index 00000000..279b50f8 --- /dev/null +++ b/app/www/library/examples/typography_h1.py @@ -0,0 +1,8 @@ +import reflex as rx + + +def header_1(): + return rx.el.h1( + "All we have to decide is what to do with the time that is given us.", + class_name="scroll-m-20 text-center text-4xl font-extrabold tracking-tight text-balance", + ) diff --git a/app/www/library/examples/typography_h2.py b/app/www/library/examples/typography_h2.py new file mode 100644 index 00000000..40dba7d3 --- /dev/null +++ b/app/www/library/examples/typography_h2.py @@ -0,0 +1,8 @@ +import reflex as rx + + +def header_2(): + return rx.el.h2( + "Not all those who wander are lost.", + class_name="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0", + ) diff --git a/app/www/library/examples/typography_h3.py b/app/www/library/examples/typography_h3.py new file mode 100644 index 00000000..066f5c2c --- /dev/null +++ b/app/www/library/examples/typography_h3.py @@ -0,0 +1,8 @@ +import reflex as rx + + +def header_3(): + return rx.el.h3( + "The Grey Havens lie beyond the reach of mortal sight.", + class_name="scroll-m-20 text-2xl font-semibold tracking-tight", + ) diff --git a/app/www/library/examples/typography_h4.py b/app/www/library/examples/typography_h4.py new file mode 100644 index 00000000..0e0244a5 --- /dev/null +++ b/app/www/library/examples/typography_h4.py @@ -0,0 +1,8 @@ +import reflex as rx + + +def header_4(): + return rx.el.h4( + "Far over the misty mountains cold.", + class_name="scroll-m-20 text-xl font-semibold tracking-tight", + ) diff --git a/app/www/library/examples/typography_inline_code.py b/app/www/library/examples/typography_inline_code.py new file mode 100644 index 00000000..61c13ec4 --- /dev/null +++ b/app/www/library/examples/typography_inline_code.py @@ -0,0 +1,8 @@ +import reflex as rx + + +def inline_code(): + return rx.el.code( + "@buridan-ui/ui/accordion", + class_name="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-medium", + ) diff --git a/app/www/library/examples/typography_list.py b/app/www/library/examples/typography_list.py new file mode 100644 index 00000000..b9d54a8d --- /dev/null +++ b/app/www/library/examples/typography_list.py @@ -0,0 +1,10 @@ +import reflex as rx + + +def list(): + return rx.el.ul( + rx.el.li("Not all those who wander are lost."), + rx.el.li("Even darkness must pass."), + rx.el.li("Courage will now be your best defence."), + class_name="my-6 ml-6 list-disc [&>li]:mt-2", + ) diff --git a/app/www/library/examples/typography_p.py b/app/www/library/examples/typography_p.py new file mode 100644 index 00000000..ce17530d --- /dev/null +++ b/app/www/library/examples/typography_p.py @@ -0,0 +1,8 @@ +import reflex as rx + + +def paragraph(): + return rx.el.p( + "I have seen many an oak grow from acorn to ruinous age.", + class_name="leading-7 [&:not(:first-child)]:mt-6", + ) diff --git a/app/www/library/examples/typography_table.py b/app/www/library/examples/typography_table.py new file mode 100644 index 00000000..756ee309 --- /dev/null +++ b/app/www/library/examples/typography_table.py @@ -0,0 +1,58 @@ +import reflex as rx + + +def table_list(): + return rx.el.div( + rx.el.table( + rx.el.thead( + rx.el.tr( + rx.el.th( + "Character", + class_name="border border-input px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right", + ), + rx.el.th( + "Role", + class_name="border border-input px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right", + ), + class_name="m-0 border-t border-input p-0 even:bg-muted", + ) + ), + rx.el.tbody( + rx.el.tr( + rx.el.td( + "Frodo", + class_name="border border-input px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right", + ), + rx.el.td( + "Ring Bearer", + class_name="border border-input px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right", + ), + class_name="m-0 border-t border-input p-0 even:bg-muted", + ), + rx.el.tr( + rx.el.td( + "Gandlaf", + class_name="border border-input px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right", + ), + rx.el.td( + "The Grey / White Wizard", + class_name="border border-input px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right", + ), + class_name="m-0 border-t border-input p-0 even:bg-muted", + ), + rx.el.tr( + rx.el.td( + "Legolas", + class_name="border border-input px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right", + ), + rx.el.td( + "Elven archer", + class_name="border border-input px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right", + ), + class_name="m-0 border-t border-input p-0 even:bg-muted", + ), + ), + class_name="w-full", + ), + class_name="my-6 w-full overflow-y-auto", + ) diff --git a/src/docs/library/getting_started/charting_components.py b/app/www/library/getting_started/charting_components.py similarity index 100% rename from src/docs/library/getting_started/charting_components.py rename to app/www/library/getting_started/charting_components.py diff --git a/src/docs/library/getting_started/clientstate_components.py b/app/www/library/getting_started/clientstate_components.py similarity index 100% rename from src/docs/library/getting_started/clientstate_components.py rename to app/www/library/getting_started/clientstate_components.py diff --git a/src/docs/library/getting_started/dashboard_components.py b/app/www/library/getting_started/dashboard_components.py similarity index 100% rename from src/docs/library/getting_started/dashboard_components.py rename to app/www/library/getting_started/dashboard_components.py diff --git a/src/docs/library/getting_started/theming_components.py b/app/www/library/getting_started/theming_components.py similarity index 100% rename from src/docs/library/getting_started/theming_components.py rename to app/www/library/getting_started/theming_components.py diff --git a/app/www/parser.py b/app/www/parser.py new file mode 100644 index 00000000..53c847f0 --- /dev/null +++ b/app/www/parser.py @@ -0,0 +1,130 @@ +import ast +import importlib +import inspect +import pathlib +import re +from pathlib import Path +from typing import Callable, Dict, List + +import reflex as rx + +from app.www.style import markdown_component_map, render_parse_error +from app.www.wrapper import ( + chart_util_wrapper, + cli_and_manual_installation_wrapper, + demo_wrapper, + usage_wrapper, +) + + +class DocParser: + """Minimal markdown parser for Reflex documentation.""" + + def __init__( + self, registry: Dict[str, Callable] = None, dynamic_load_dirs: List[str] = None + ): + self.registry = registry or {} + root = pathlib.Path(__file__).parent.parent.parent + for d in dynamic_load_dirs or []: + for py in (root / d).rglob("*.py"): + if py.name.startswith("__"): + continue + mod = importlib.import_module( + ".".join(py.relative_to(root).with_suffix("").parts) + ) + for n, o in inspect.getmembers(mod): + if ( + inspect.isfunction(o) + or inspect.isclass(o) + or isinstance(o, rx.ComponentNamespace) + ) and o.__module__ == mod.__name__: + self.registry[n.lower()] = o + + def _render(self, cmd: str, arg: str) -> rx.Component: + try: + val = ( + ast.literal_eval(arg) + if arg and any(arg.startswith(c) for c in "[{'\"") + else arg + ) + name = val[0] if isinstance(val, list) else val + obj = self.registry.get(str(name).lower()) + if not obj: + return render_parse_error(f"'{name}' not found") + + # Use the class for inspection if obj is an instance (like ComponentNamespace) + inspect_obj = obj + if not ( + inspect.isclass(obj) or inspect.isfunction(obj) or inspect.ismodule(obj) + ): + inspect_obj = getattr(obj, "__class__", obj) + + # Check if it's a chart-related component + try: + file_path = inspect.getfile(inspect_obj) + is_chart = "charts" in file_path + except (TypeError, ValueError): + is_chart = False + file_path = "" + + if "demo" in cmd: + # src = inspect.getsource(obj).strip() + src = Path(inspect.getsourcefile(obj)).read_text().strip() + return demo_wrapper(obj(), src) + + if "code" in cmd: + src = ( + inspect.getsource(inspect.getmodule(inspect_obj)) + if "_file" in cmd + else inspect.getsource(obj) + ).strip() + + return chart_util_wrapper(source=src) + + if cmd == "install": + mod = inspect.getmodule(inspect_obj) + if not mod: + return render_parse_error(f"Module not found for {name}") + src = inspect.getsource(mod).strip() + return cli_and_manual_installation_wrapper(val[1], src, val[0]) + + if cmd == "usage": + import_name = getattr(obj, "__name__", str(name)) + module_stem = pathlib.Path(file_path).stem if file_path else "component" + return usage_wrapper( + f"from components.ui.{module_stem} import {import_name}" + ) + + if cmd == "anatomy": + from app.www.anatomy import ANATOMY + + src = ANATOMY.get(str(name).lower()) + if not src: + return render_parse_error(f"No anatomy found for '{name}'") + return rx.el.div( + rx.el.code( + src, + style={ + "white-space": "pre", + "color": "var(--foreground)", + "font-size": "13px", + "padding": "1rem 0.75rem", + "display": "block", + }, + ), + class_name="w-full mt-4 mb-8 rounded-[0.625rem] outline outline-input flex-1 min-h-0 flex flex-col bg-secondary dark:bg-card", + ) + + return render_parse_error(f"Unknown command: {cmd}") + except Exception as e: + return render_parse_error(f"Error in {cmd}: {e}") + + def parse_and_render(self, content: str) -> List[rx.Component]: + res = [] + for s in re.split(r"(--[\w_]+(?:\([^)]+\))?--)", content): + if m := re.match(r"--([\w_]+)(?:\(([^)]+)\))?--", s): + res.append(self._render(m.group(1).lower(), m.group(2))) + elif t := s.strip(): + res.append(rx.markdown(t, component_map=markdown_component_map)) + + return res diff --git a/app/www/style.py b/app/www/style.py new file mode 100644 index 00000000..a20a9a49 --- /dev/null +++ b/app/www/style.py @@ -0,0 +1,62 @@ +import random +import string + +import reflex as rx + +# --- Markdown Styles --- +# PARAGRAPH_CLASS = "text-sm leading-7 mb-4" +# HEADING_1_CLASS = "text-2xl" +# HEADING_2_CLASS = "text-xl mt-2" +# LIST_ITEM_CLASS = "text-sm text-slate-11" +# LINK_CLASS = "text-accent-8" + +PARAGRAPH_CLASS = "text-sm leading-7 mb-4" + +HEADING_1_CLASS = "text-2xl font-semibold mt-8 mb-3 first:mt-0" + +HEADING_2_CLASS = "text-xl font-semibold mt-6 mb-2" + +LIST_ITEM_CLASS = "text-sm leading-7 text-slate-11" + +LINK_CLASS = "text-accent-8 underline-offset-2 hover:underline" + + +# --- Helper functions to generate ClientStateVar names --- +def generate_component_id(): + """Generate a unique component ID.""" + return "".join(random.choices(string.ascii_letters + string.digits, k=10)) + + +# --- Helper error functions during parsing --- +def render_parse_error(msg: str): + return rx.el.p(msg, class_name="text-sm text-red-500") + + +# --- Helper functions --- +def render_heading(level: int, text: str) -> rx.Component: + return rx.heading( + text, class_name=HEADING_1_CLASS if level == 1 else HEADING_2_CLASS, id=text + ) + + +def render_paragraph(text: str) -> rx.Component: + return rx.text(text, class_name=PARAGRAPH_CLASS) + + +def render_list_item(text: str) -> rx.Component: + return rx.list_item(rx.text(text, class_name=LIST_ITEM_CLASS)) + + +def render_link(text: str, **props) -> rx.Component: + return rx.link(text, class_name=LINK_CLASS, **props) + + +# --- Final Component Map --- +markdown_component_map = { + "h1": lambda text: render_heading(1, text), + "h2": lambda text: render_heading(2, text), + "p": render_paragraph, + "li": render_list_item, + "a": render_link, + "code": lambda text: rx.el.span(text, class_name="bg-secondary rounded-md p-1"), +} diff --git a/app/www/wrapper.py b/app/www/wrapper.py new file mode 100644 index 00000000..921d717b --- /dev/null +++ b/app/www/wrapper.py @@ -0,0 +1,321 @@ +import json +import random +import string + +import reflex as rx + +from components.icons.hugeicon import hi +from components.ui.button import button + + +def get_ui_base_files(): + base_ui = open("components/ui/base_ui.py").read() + components = open("components/ui/component.py").read() + twmerge = open("components/utils/twmerge.py").read() + return base_ui, components, twmerge + + +def styled_tab_trigger(label: str, value: str) -> rx.Component: + base_tab_style = { + "width": "flex-1", + "border": "none", + "background": "transparent", + "&[data-state=active]": { + "border": "none", + "borderBottom": rx.color_mode_cond( + "1.25px solid black", "1.25px solid white" + ), + "background": "transparent", + }, + "&[data-state=inactive]": { + "border": "none", + "background": "transparent", + }, + "&::before": { + "display": "none", + }, + } + + return rx.tabs.trigger(rx.el.p(label), value=value, style=base_tab_style) + + +def generate_component_id() -> str: + """Generate a unique component ID.""" + return "".join(random.choices(string.ascii_letters + string.digits, k=10)) + + +def create_copy_button(content: str) -> rx.Component: + uid = generate_component_id() + btn_id = f"btn-{uid}" + icon_id = f"icon-{uid}" + copy_icon_svg = '' + tick_icon_svg = '' + safe_content = json.dumps(content) + + return rx.el.button( + hi("Copy01Icon", id=icon_id, class_name="size-4"), + id=btn_id, + class_name="px-[0.75rem]", + on_click=rx.call_script( + f""" + const icon = document.getElementById('{icon_id}'); + navigator.clipboard.writeText({safe_content}); + + // Swap to tick + icon.innerHTML = `{tick_icon_svg}`; + + // Revert after 1.5s + setTimeout(() => {{ + icon.innerHTML = `{copy_icon_svg}`; + }}, 1500); + """ + ), + ) + + +def file_codeblock(file_path: str, source: str) -> rx.Component: + toggle_height_id = generate_component_id() + parts = file_path.rsplit("/", 1) + dir_part = parts[0] + "/" if len(parts) > 1 else "" + file_part = parts[1] if len(parts) > 1 else parts[0] + + return rx.el.div( + rx.el.div( + rx.el.div( + rx.html( + """ + Python + """, + class_name="text-foreground size-4", + ), + rx.el.p( + dir_part, + rx.el.span(rx.el.strong(file_part)), + class_name="text-muted-foreground text-sm font-normal", + ), + class_name="flex flex-row gap-x-2 items-center px-[0.75rem] py-2", + ), + rx.el.div( + button( + "Expand", + class_name="!text-sm text-foreground", + id=f"trigger-{toggle_height_id}", + on_click=rx.call_script( + f""" + const panel = document.getElementById('code-panel-{toggle_height_id}'); + const btn = document.getElementById('trigger-{toggle_height_id}'); + + if (panel.style.maxHeight === 'none') {{ + panel.style.maxHeight = '40vh'; + panel.style.overflow = 'hidden'; + panel.style.maskImage = 'linear-gradient(to bottom, black 65%, transparent 100%)'; + panel.style.webkitMaskImage = 'linear-gradient(to bottom, black 65%, transparent 100%)'; + btn.innerText = 'Expand'; + }} else {{ + panel.style.maxHeight = 'none'; + panel.style.overflow = 'auto'; + panel.style.maskImage = 'none'; + panel.style.webkitMaskImage = 'none'; + btn.innerText = 'Collapse'; + }} + """ + ), + size="sm", + variant="ghost", + ), + create_copy_button(content=source), + class_name="flex flex-row gap-x-2 items-center", + ), + class_name="w-full border-b border-input flex flex-row items-center justify-between", + ), + rx.el.div( + rx.el.code( + source, + style={ + "white-space": "pre", + "color": "var(--foreground)", + "font-size": "13px", + "padding": "1rem 0.75rem", + "display": "block", + }, + ), + id=f"code-panel-{toggle_height_id}", + style={ + "max-height": "40vh", + "overflow": "hidden", + "transition": "max-height 0.3s ease-in-out", + "mask-image": "linear-gradient(to bottom, black 65%, transparent 100%)", + "-webkit-mask-image": "linear-gradient(to bottom, black 65%, transparent 100%)", + }, + class_name="scrollbar-none flex-1 min-h-0 flex flex-col h-full", + ), + class_name="rounded-radius outline outline-input flex-1 min-h-0 flex flex-col bg-secondary dark:bg-card", + ) + + +def chart_util_wrapper(source: str): + return rx.el.div( + rx.el.div( + rx.el.div( + create_copy_button(content=source), + class_name="absolute top-2 right-2 z-10 bg-secondary dark:bg-card", + ), + rx.el.div( + rx.el.pre( + rx.el.code( + source, + class_name="language-python", + on_mount=rx.call_script("Prism.highlightAll()"), + ), + style={ + "padding": "1rem 0.75rem", + }, + class_name="w-full h-full !bg-secondary dark:!bg-card !text-sm", + ), + class_name="max-h-[500px] overflow-auto scrollbar-none", + ), + class_name="bg-secondary dark:bg-card relative overflow-hidden", + ), + class_name="w-full border border-input rounded-radius mb-8 !overflow-hidden", + ) + + +def demo_wrapper(component: rx.Component, source: str) -> rx.Component: + + return rx.el.div( + rx.el.div( + component, + class_name="min-h-[250px] flex items-center justify-center p-6", + ), + rx.el.div( + rx.el.div( + create_copy_button(content=source), + class_name="absolute top-2 right-2 z-10 bg-secondary dark:bg-card", + ), + rx.el.div( + rx.el.pre( + rx.el.code( + source, + class_name="language-python", + on_mount=rx.call_script("Prism.highlightAll()"), + ), + style={ + "padding": "1rem 0.75rem", + }, + class_name="w-full h-full !bg-secondary dark:!bg-card !text-sm", + ), + class_name="max-h-[250px] overflow-auto scrollbar-none", + ), + class_name="border-t border-input bg-secondary dark:bg-card relative overflow-hidden", + ), + class_name="w-full border border-input rounded-radius flex flex-col mb-8 !overflow-hidden", + ) + + +def usage_wrapper(import_path: str) -> rx.Component: + """Wrapper for the usage section showing how to import the component.""" + return rx.el.div( + rx.el.code( + import_path, + style={ + "white-space": "pre", + "color": "var(--foreground)", + "font-size": "13px", + "padding": "1rem 0.75rem", + "display": "block", + }, + ), + class_name="w-full mt-4 mb-8 rounded-[0.625rem] outline outline-input flex-1 min-h-0 flex flex-col bg-secondary dark:bg-card", + ) + + +def cli_and_manual_installation_wrapper( + cli_command: str, source: str, file_name: str +) -> rx.Component: + """Tabbed wrapper for CLI and Manual installation instructions.""" + base_ui_source, components_source, twmerge_source = get_ui_base_files() + + tab_list_style = { + "border": "none", + "boxShadow": "none", + "background": "transparent", + } + + manual_title_style = "text-muted-foreground text-md font-semibold" + + return rx.tabs.root( + rx.tabs.list( + styled_tab_trigger("Command", "cli"), + styled_tab_trigger("Manual", "manual"), + style=tab_list_style, + ), + rx.tabs.content( + rx.el.div( + rx.el.div( + rx.el.div( + rx.el.div( + rx.html( + """ + + + + + + """, + class_name="text-foreground", + ), + rx.el.p( + "uv", + style={ + "font-size": "0.875rem", + "line-height": "1", + "display": "block", + "overflow": "hidden", + }, + class_name="text-foreground font-normal font-theme", + ), + class_name="flex flex-row gap-x-2 items-center px-[0.75rem] py-2", + ), + create_copy_button(content=cli_command), + class_name="w-full border-b border-input flex flex-row items-center justify-between", + ), + rx.el.div( + rx.el.code( + cli_command, + style={ + "white-space": "pre", + "color": "var(--foreground)", + "font-size": "13px", + "padding": "1rem 0.75rem", + "display": "block", + }, + ), + class_name="overflow-x-auto overflow-y-auto scrollbar-none flex-1 min-h-0", + ), + class_name="w-full flex-1 min-h-0 flex flex-col h-full", + ), + class_name="rounded-[0.625rem] outline outline-input flex-1 min-h-0 flex flex-col bg-secondary dark:bg-card", + ), + value="cli", + class_name="mt-6", + ), + rx.tabs.content( + rx.el.div( + rx.el.p("1. Base UI package library", class_name=manual_title_style), + file_codeblock("components/ui/base_ui.py", base_ui_source), + rx.el.p("2. Component library", class_name=manual_title_style), + file_codeblock("components/ui/component.py", components_source), + rx.el.p( + "3. Tailwind merge utility file", class_name=manual_title_style + ), + file_codeblock("components/utils/twmerge.py", twmerge_source), + rx.el.p("4. UI component file", class_name=manual_title_style), + file_codeblock(f"components/ui/{file_name.lower()}.py", source), + class_name="flex flex-col gap-y-4", + ), + value="manual", + class_name="mt-6", + ), + default_value="cli", + class_name="mb-8", + ) diff --git a/assets/css/wrapper.css b/assets/css/wrapper.css deleted file mode 100644 index 88876a4e..00000000 --- a/assets/css/wrapper.css +++ /dev/null @@ -1,331 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); - --chart-1: oklch(0.4 0 0); - --chart-2: oklch(0.55 0 0); - --chart-3: oklch(0.7 0 0); - --chart-4: oklch(0.82 0 0); - --chart-5: oklch(0.92 0 0); -} - -.dark { - --radius: 0.625rem; - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); - --chart-1: oklch(0.4 0 0); - --chart-2: oklch(0.55 0 0); - --chart-3: oklch(0.7 0 0); - --chart-4: oklch(0.82 0 0); - --chart-5: oklch(0.92 0 0); -} - -.theme-blue { - --foreground: oklch(0.141 0.005 285.823); - --card-foreground: oklch(0.141 0.005 285.823); - --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.623 0.214 259.815); - --primary-foreground: oklch(0.97 0.014 254.604); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.967 0.001 286.375); - --muted-foreground: oklch(0.552 0.016 285.938); - --accent: oklch(0.967 0.001 286.375); - --accent-foreground: oklch(0.21 0.006 285.885); - --border: oklch(0.92 0.004 286.32); - --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.623 0.214 259.815); - --chart-1: oklch(0.81 0.1 252); - --chart-2: oklch(0.62 0.19 260); - --chart-3: oklch(0.55 0.22 263); - --chart-4: oklch(0.49 0.22 264); - --chart-5: oklch(0.42 0.18 266); - --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.623 0.214 259.815); - --sidebar-primary-foreground: oklch(0.97 0.014 254.604); - --sidebar-accent: oklch(0.967 0.001 286.375); - --sidebar-accent-foreground: oklch(0.21 0.006 285.885); - --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.623 0.214 259.815); -} - -.theme-blue-dark { - --background: oklch(0.141 0.005 285.823); - --card: oklch(0.21 0.006 285.885); - --popover: oklch(0.21 0.006 285.885); - --primary: oklch(0.546 0.245 262.881); - --secondary: oklch(0.274 0.006 286.033); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); - --ring: oklch(0.488 0.243 264.376); - --chart-1: oklch(0.81 0.1 252); - --chart-2: oklch(0.62 0.19 260); - --chart-3: oklch(0.55 0.22 263); - --chart-4: oklch(0.49 0.22 264); - --chart-5: oklch(0.42 0.18 266); - --sidebar: oklch(0.21 0.006 285.885); - --sidebar-primary: oklch(0.546 0.245 262.881); - --sidebar-primary-foreground: oklch(0.379 0.146 265.522); - --sidebar-accent: oklch(0.274 0.006 286.033); - --sidebar-ring: oklch(0.488 0.243 264.376); -} - -.theme-red { - --chart-1: oklch(0.81 0.1 20); - --chart-2: oklch(0.64 0.21 25); - --chart-3: oklch(0.58 0.22 27); - --chart-4: oklch(0.51 0.19 28); - --chart-5: oklch(0.44 0.16 27); - --foreground: oklch(0.141 0.005 285.823); - --card-foreground: oklch(0.141 0.005 285.823); - --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.637 0.237 25.331); - --primary-foreground: oklch(0.971 0.013 17.38); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.967 0.001 286.375); - --muted-foreground: oklch(0.552 0.016 285.938); - --accent: oklch(0.967 0.001 286.375); - --accent-foreground: oklch(0.21 0.006 285.885); - --border: oklch(0.92 0.004 286.32); - --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.637 0.237 25.331); - --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.637 0.237 25.331); - --sidebar-primary-foreground: oklch(0.971 0.013 17.38); - --sidebar-accent: oklch(0.967 0.001 286.375); - --sidebar-accent-foreground: oklch(0.21 0.006 285.885); - --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.637 0.237 25.331); -} - -.theme-red-dark { - --chart-1: oklch(0.81 0.1 20); - --chart-2: oklch(0.64 0.21 25); - --chart-3: oklch(0.58 0.22 27); - --chart-4: oklch(0.51 0.19 28); - --chart-5: oklch(0.44 0.16 27); - --background: oklch(0.141 0.005 285.823); - --card: oklch(0.21 0.006 285.885); - --popover: oklch(0.21 0.006 285.885); - --primary: oklch(0.637 0.237 25.331); - --primary-foreground: oklch(0.971 0.013 17.38); - --secondary: oklch(0.274 0.006 286.033); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); - --ring: oklch(0.637 0.237 25.331); - --sidebar-primary: oklch(0.637 0.237 25.331); - --sidebar-primary-foreground: oklch(0.971 0.013 17.38); - --sidebar-accent: oklch(0.274 0.006 286.033); - --sidebar-ring: oklch(0.637 0.237 25.331); -} - -.theme-green { - --foreground: oklch(0.141 0.005 285.823); - --card-foreground: oklch(0.141 0.005 285.823); - --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.723 0.219 149.579); - --primary-foreground: oklch(0.982 0.018 155.826); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.967 0.001 286.375); - --muted-foreground: oklch(0.552 0.016 285.938); - --accent: oklch(0.967 0.001 286.375); - --accent-foreground: oklch(0.21 0.006 285.885); - --border: oklch(0.92 0.004 286.32); - --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.723 0.219 149.579); - --chart-1: oklch(0.87 0.14 154); - --chart-2: oklch(0.72 0.19 150); - --chart-3: oklch(0.63 0.17 149); - --chart-4: oklch(0.53 0.14 150); - --chart-5: oklch(0.45 0.11 151); - --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.723 0.219 149.579); - --sidebar-primary-foreground: oklch(0.982 0.018 155.826); - --sidebar-accent: oklch(0.967 0.001 286.375); - --sidebar-accent-foreground: oklch(0.21 0.006 285.885); - --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.723 0.219 149.579); -} - -.theme-green-dark { - --background: oklch(0.141 0.005 285.823); - --card: oklch(0.21 0.006 285.885); - --popover: oklch(0.21 0.006 285.885); - --primary: oklch(0.696 0.17 162.48); - --primary-foreground: oklch(0.393 0.095 152.535); - --secondary: oklch(0.274 0.006 286.033); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); - --ring: oklch(0.527 0.154 150.069); - --chart-1: oklch(0.87 0.14 154); - --chart-2: oklch(0.72 0.19 150); - --chart-3: oklch(0.63 0.17 149); - --chart-4: oklch(0.53 0.14 150); - --chart-5: oklch(0.45 0.11 151); - --sidebar: oklch(0.21 0.006 285.885); - --sidebar-primary: oklch(0.696 0.17 162.48); - --sidebar-primary-foreground: oklch(0.393 0.095 152.535); - --sidebar-accent: oklch(0.274 0.006 286.033); - --sidebar-ring: oklch(0.527 0.154 150.069); -} - -.theme-amber { - --foreground: oklch(0.141 0.005 285.823); - --card-foreground: oklch(0.141 0.005 285.823); - --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.795 0.184 86.047); - --primary-foreground: oklch(0.421 0.095 57.708); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.967 0.001 286.375); - --muted-foreground: oklch(0.552 0.016 285.938); - --accent: oklch(0.967 0.001 286.375); - --accent-foreground: oklch(0.21 0.006 285.885); - --border: oklch(0.92 0.004 286.32); - --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.795 0.184 86.047); - --chart-1: oklch(0.88 0.15 92); - --chart-2: oklch(0.77 0.16 70); - --chart-3: oklch(0.67 0.16 58); - --chart-4: oklch(0.56 0.15 49); - --chart-5: oklch(0.47 0.12 46); - --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.795 0.184 86.047); - --sidebar-primary-foreground: oklch(0.421 0.095 57.708); - --sidebar-accent: oklch(0.967 0.001 286.375); - --sidebar-accent-foreground: oklch(0.21 0.006 285.885); - --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.795 0.184 86.047); -} - -.theme-amber-dark { - --background: oklch(0.141 0.005 285.823); - --card: oklch(0.21 0.006 285.885); - --popover: oklch(0.21 0.006 285.885); - --primary: oklch(0.795 0.184 86.047); - --primary-foreground: oklch(0.421 0.095 57.708); - --secondary: oklch(0.274 0.006 286.033); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); - --ring: oklch(0.554 0.135 66.442); - --chart-1: oklch(0.88 0.15 92); - --chart-2: oklch(0.77 0.16 70); - --chart-3: oklch(0.67 0.16 58); - --chart-4: oklch(0.56 0.15 49); - --chart-5: oklch(0.47 0.12 46); - --sidebar: oklch(0.21 0.006 285.885); - --sidebar-primary: oklch(0.795 0.184 86.047); - --sidebar-primary-foreground: oklch(0.421 0.095 57.708); - --sidebar-accent: oklch(0.274 0.006 286.033); - --sidebar-ring: oklch(0.554 0.135 66.442); -} - -.theme-purple { - --foreground: oklch(0.141 0.005 285.823); - --card-foreground: oklch(0.141 0.005 285.823); - --popover-foreground: oklch(0.141 0.005 285.823); - --primary: oklch(0.606 0.25 292.717); - --primary-foreground: oklch(0.969 0.016 293.756); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.967 0.001 286.375); - --muted-foreground: oklch(0.552 0.016 285.938); - --accent: oklch(0.967 0.001 286.375); - --accent-foreground: oklch(0.21 0.006 285.885); - --border: oklch(0.92 0.004 286.32); - --input: oklch(0.92 0.004 286.32); - --ring: oklch(0.606 0.25 292.717); - --chart-1: oklch(0.83 0.11 306); - --chart-2: oklch(0.63 0.23 304); - --chart-3: oklch(0.56 0.25 302); - --chart-4: oklch(0.5 0.24 302); - --chart-5: oklch(0.44 0.2 304); - --sidebar-foreground: oklch(0.141 0.005 285.823); - --sidebar-primary: oklch(0.606 0.25 292.717); - --sidebar-primary-foreground: oklch(0.969 0.016 293.756); - --sidebar-accent: oklch(0.967 0.001 286.375); - --sidebar-accent-foreground: oklch(0.21 0.006 285.885); - --sidebar-border: oklch(0.92 0.004 286.32); - --sidebar-ring: oklch(0.606 0.25 292.717); -} - -.theme-purple-dark { - --background: oklch(0.141 0.005 285.823); - --card: oklch(0.21 0.006 285.885); - --popover: oklch(0.21 0.006 285.885); - --primary: oklch(0.541 0.281 293.009); - --primary-foreground: oklch(0.969 0.016 293.756); - --secondary: oklch(0.274 0.006 286.033); - --muted: oklch(0.274 0.006 286.033); - --muted-foreground: oklch(0.705 0.015 286.067); - --accent: oklch(0.274 0.006 286.033); - --ring: oklch(0.541 0.281 293.009); - --chart-1: oklch(0.83 0.11 306); - --chart-2: oklch(0.63 0.23 304); - --chart-3: oklch(0.56 0.25 302); - --chart-4: oklch(0.5 0.24 302); - --chart-5: oklch(0.44 0.2 304); - --sidebar: oklch(0.21 0.006 285.885); - --sidebar-primary: oklch(0.541 0.281 293.009); - --sidebar-primary-foreground: oklch(0.969 0.016 293.756); - --sidebar-accent: oklch(0.274 0.006 286.033); - --sidebar-ring: oklch(0.541 0.281 293.009); -} diff --git a/assets/docs/-components/avatar.md b/assets/docs/-components/avatar.md deleted file mode 100644 index b66d851b..00000000 --- a/assets/docs/-components/avatar.md +++ /dev/null @@ -1,261 +0,0 @@ - - -# Avatar - -An image element with a fallback for representing the user. - -# Installation - -Copy the following code into your app directory. - - -```python -"""Custom avatar component.""" - -from reflex.components.component import Component, ComponentNamespace -from reflex.event import EventHandler, passthrough_event_spec -from reflex.utils.imports import ImportVar -from reflex.vars.base import Var - -from ..base_ui import PACKAGE_NAME, BaseUIComponent - - -class ClassNames: - """Class names for avatar components.""" - - ROOT = "shrink-0 inline-flex size-8 items-center justify-center overflow-hidden rounded-full align-middle text-base font-medium select-none" - IMAGE = "size-full object-cover shrink-0" - FALLBACK = "flex size-full items-center justify-center text-sm bg-muted" - - -class AvatarBaseComponent(BaseUIComponent): - """Base component for avatar components.""" - - library = f"{PACKAGE_NAME}/avatar" - - @property - def import_var(self): - """Return the import variable for the avatar component.""" - return ImportVar(tag="Avatar", package_path="", install=False) - - -class AvatarRoot(AvatarBaseComponent): - """Displays a user's profile picture, initials, or fallback icon.""" - - tag = "Avatar.Root" - - # The component to render - render_: Var[Component] - - @classmethod - def create(cls, *children, **props) -> BaseUIComponent: - """Create the avatar root component.""" - props["data-slot"] = "avatar" - cls.set_class_name(ClassNames.ROOT, props) - return super().create(*children, **props) - - -class AvatarImage(AvatarBaseComponent): - """The image to be displayed in the avatar.""" - - tag = "Avatar.Image" - - # The image source URL - src: Var[str] - - # Callback when loading status changes - on_loading_status_change: EventHandler[passthrough_event_spec(str)] - - # The component to render - render_: Var[Component] - - @classmethod - def create(cls, *children, **props) -> BaseUIComponent: - """Create the avatar image component.""" - props["data-slot"] = "avatar-image" - cls.set_class_name(ClassNames.IMAGE, props) - return super().create(*children, **props) - - -class AvatarFallback(AvatarBaseComponent): - """Rendered when the image fails to load or when no image is provided.""" - - tag = "Avatar.Fallback" - - # How long to wait before showing the fallback. Specified in milliseconds - delay: Var[int] - - # The component to render - render_: Var[Component] - - @classmethod - def create(cls, *children, **props) -> BaseUIComponent: - """Create the avatar fallback component.""" - props["data-slot"] = "avatar-fallback" - cls.set_class_name(ClassNames.FALLBACK, props) - return super().create(*children, **props) - - -class HighLevelAvatar(AvatarRoot): - """High level wrapper for the Avatar component.""" - - # The image source URL - src: Var[str] - - # Image props - _image_props = {"src", "on_loading_status_change", "render_"} - - # Fallback props - _fallback_props = {"delay"} - - @classmethod - def create(cls, *children, **props) -> BaseUIComponent: - """Create the avatar component.""" - # Extract props for each subcomponent - image_props = {k: props.pop(k) for k in cls._image_props & props.keys()} - fallback_props = {k: props.pop(k) for k in cls._fallback_props & props.keys()} - - fallback_content = props.pop("fallback", "") - - return AvatarRoot.create( - AvatarImage.create(**image_props), - AvatarFallback.create(fallback_content, **fallback_props), - *children, - **props, - ) - - -class Avatar(ComponentNamespace): - """Namespace for Avatar components.""" - - root = staticmethod(AvatarRoot.create) - image = staticmethod(AvatarImage.create) - fallback = staticmethod(AvatarFallback.create) - class_names = ClassNames - __call__ = staticmethod(HighLevelAvatar.create) - - -avatar = Avatar() -``` - - -# Examples - -## General - -Displays a basic avatar with either a user image or a fallback placeholder. - - -```python -def avatar_example(): - return rx.box( - avatar( - src="https://avatars.githubusercontent.com/u/84860195?v=4", - alt="@LineIndent", - fallback="CN", - ), - avatar( - src="https://avatars.githubusercontent.com/u/198465274?s=200&v=4", - alt="@buridan-ui", - fallback="BUI", - class_name="rounded-lg", - ), - rx.box( - avatar( - src="", - alt="@buridan-ui", - fallback="BU", - ), - avatar( - src="https://avatars.githubusercontent.com/u/84860195?v=4", - alt="@buridan-ui", - fallback="BUI", - ), - avatar( - src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", - alt="@reflex", - fallback="RE", - ), - class_name=( - "flex -space-x-2 " - "*:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-[var(--background)] " - "*:data-[slot=avatar]:grayscale" - ), - ), - class_name="flex flex-row flex-wrap items-center gap-12 p-8", - ) -``` - - -## Sizes - -Demonstrates how to scale the avatar component using Tailwind utility classes. - - -```python -def avatar_sizes(): - """Example showing different avatar sizes""" - return rx.box( - avatar( - src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", - alt="@reflex", - fallback="RE", - class_name="size-6", - ), - avatar( - src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", - alt="@reflex", - fallback="RE", - class_name="size-8", - ), - avatar( - src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", - alt="@reflex", - fallback="RE", - class_name="size-10", - ), - avatar( - src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", - alt="@reflex", - fallback="RE", - class_name="size-12", - ), - avatar( - src="https://avatars.githubusercontent.com/u/104714959?s=200&v=4", - alt="@reflex", - fallback="RE", - class_name="size-16", - ), - class_name="flex items-center gap-4 p-8", - ) -``` - - -## With Badge - -Shows how to combine an avatar with status or notification badges for added context. - - -```python -def avatar_with_badge(): - """Example showing avatar with status badge""" - return rx.box( - rx.box( - avatar( - src="https://avatars.githubusercontent.com/u/84860195?v=4", - alt="@LineIndent", - fallback="CN", - class_name="size-12", - ), - rx.box( - class_name=( - "absolute bottom-0 right-0 size-3 rounded-full " - "bg-green-500 border-2 border-[var(--background)]" - ), - ), - class_name="relative inline-block", - ), - class_name="p-8", - ) -``` - diff --git a/assets/docs/-components/badge.md b/assets/docs/-components/badge.md deleted file mode 100644 index d7eb2f95..00000000 --- a/assets/docs/-components/badge.md +++ /dev/null @@ -1,237 +0,0 @@ - - -# Badge - -Displays a badge or a component that looks like a badge. - -# Installation - -Copy the following code into your app directory. - - -```python -from typing import Literal - -from reflex.components.el import Span -from reflex.vars.base import Var - -from ..component import CoreComponent - -LiteralBadgeVariant = Literal["default", "secondary", "destructive", "outline"] - -DEFAULT_BASE_CLASSES = ( - "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium " - "w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none " - "focus-visible:border-[var(--ring)] focus-visible:ring-[var(--ring)]/50 focus-visible:ring-[3px] " - "aria-invalid:ring-[var(--destructive)]/20 dark:aria-invalid:ring-[var(--destructive)]/40 " - "aria-invalid:border-[var(--destructive)] transition-[color,box-shadow] overflow-hidden" -) - -BADGE_VARIANTS = { - "default": ( - "border-transparent bg-[var(--primary)] text-[var(--primary-foreground)] " - "[a&]:hover:bg-[var(--primary)]/90" - ), - "secondary": ( - "border-transparent bg-[var(--secondary)] text-[var(--secondary-foreground)] " - "[a&]:hover:bg-[var(--secondary)]/90" - ), - "destructive": ( - "border-transparent bg-[var(--destructive)] text-white " - "[a&]:hover:bg-[var(--destructive)]/90 " - "focus-visible:ring-[var(--destructive)]/20 dark:focus-visible:ring-[var(--destructive)]/40 " - "dark:bg-[var(--destructive)]/60" - ), - "outline": ( - "text-[var(--foreground)] border-[var(--input)] " - "[a&]:hover:bg-[var(--accent)] [a&]:hover:text-[var(--accent-foreground)]" - ), -} - - -def get_badge_classes(variant: LiteralBadgeVariant) -> str: - """Get the complete badge class string. - - Args: - variant: The badge variant to apply - - Returns: - The complete class string for the badge - """ - variant_classes = BADGE_VARIANTS[variant] - return f"{DEFAULT_BASE_CLASSES} {variant_classes}" - - -class Badge(Span, CoreComponent): - """A badge component that displays a label.""" - - # Badge variant - variant: Var[LiteralBadgeVariant] - - @classmethod - def create(cls, *children, **props) -> Span: - """Create the badge component. - - Args: - *children: The badge content - **props: Component properties including variant - - Returns: - A configured Span component - """ - variant = props.pop("variant", "default") - - cls.set_class_name(get_badge_classes(variant), props) - - # Add data-slot attribute - props.setdefault("data_slot", "badge") - - return super().create(*children, **props) - - def _exclude_props(self) -> list[str]: - """Exclude component-specific props from being passed to the DOM. - - Returns: - List of prop names to exclude - """ - return [*super()._exclude_props(), "variant"] - - -badge = Badge.create -``` - - -# Examples - -## Default - -Displays a standard badge using the default variant, ideal for basic labeling. - - -```python -def badge_demo(): - return rx.box( - rx.box( - badge("Badge"), - badge("Secondary", variant="secondary"), - badge("Destructive", variant="destructive"), - badge("Outline", variant="outline"), - class_name="flex w-full flex-wrap gap-2", - ), - rx.box( - badge( - rx.icon(tag="badge-check"), - "Verified", - variant="secondary", - class_name="bg-blue-500 text-white dark:bg-blue-600", - ), - badge( - "8", - class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", - ), - badge( - "99", - variant="destructive", - class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", - ), - badge( - "20+", - variant="outline", - class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", - ), - class_name="flex w-full flex-wrap gap-2", - ), - class_name="flex flex-col items-center gap-2 p-8", - ) -``` - - -## With Icons - -Demonstrates how to include icons inside badges for visual context or emphasis. - - -```python -def badge_with_icons(): - return rx.box( - badge( - rx.icon(tag="check"), - "Success", - variant="secondary", - class_name="bg-green-500 text-white dark:bg-green-600", - ), - badge( - rx.icon(tag="x"), - "Error", - variant="destructive", - ), - badge( - rx.icon(tag="triangle-alert"), - "Warning", - variant="secondary", - class_name="bg-yellow-500 text-white dark:bg-yellow-600", - ), - badge( - rx.icon(tag="info"), - "Info", - variant="secondary", - class_name="bg-blue-500 text-white dark:bg-blue-600", - ), - class_name="flex flex-wrap gap-2 p-8", - ) -``` - - -## Status - -Showcases how badges can represent different statuses, like success or error, using color. - - -```python -def badge_status_examples(): - return rx.box( - badge("New", variant="default"), - badge("Popular", variant="secondary"), - badge("Sale", variant="destructive"), - badge("Draft", variant="outline"), - badge( - rx.icon(tag="star"), - "Featured", - class_name="bg-yellow-500 text-white dark:bg-yellow-600", - ), - class_name="flex flex-wrap gap-2 p-8", - ) -``` - - -## Notification Count - -Illustrates how to use badges for showing counts, such as unread notifications or messages. - - -```python -def badge_notification_count(): - return rx.box( - badge( - "1", - class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", - ), - badge( - "5", - variant="destructive", - class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", - ), - badge( - "10", - variant="secondary", - class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", - ), - badge( - "99+", - variant="destructive", - class_name="h-5 min-w-5 rounded-full px-1 font-mono tabular-nums", - ), - class_name="flex items-center gap-2 p-8", - ) -``` - diff --git a/assets/docs/-components/breadcrumb.md b/assets/docs/-components/breadcrumb.md deleted file mode 100644 index fa4468f3..00000000 --- a/assets/docs/-components/breadcrumb.md +++ /dev/null @@ -1,259 +0,0 @@ - - -# Breadcrumb - -Displays the path to the current resource using a hierarchy of links. - -# Installation - -Copy the following code into your app directory. - - -```python -import reflex as rx - - -def breadcrumb(*children, **props): - """Breadcrumb navigation container""" - return rx.el.nav( - *children, aria_label="breadcrumb", data_slot="breadcrumb", **props - ) - - -def breadcrumb_list(*children, class_name: str = "", **props): - """Ordered list container for breadcrumb items""" - base_classes = ( - "text-[var(--muted-foreground)] flex flex-wrap items-center gap-1 text-sm " - "break-words sm:gap-2.5" - ) - - return rx.el.ol( - *children, - data_slot="breadcrumb-list", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def breadcrumb_item(*children, class_name: str = "", **props): - """Individual breadcrumb item""" - base_classes = "inline-flex items-center gap-1.5" - - return rx.el.li( - *children, - data_slot="breadcrumb-item", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def breadcrumb_link(*children, href: str = "#", class_name: str = "", **props): - """Breadcrumb link (clickable)""" - base_classes = "hover:text-[var(--foreground)] transition-colors no-underline" - - return rx.el.a( - *children, - href=href, - data_slot="breadcrumb-link", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def breadcrumb_page(*children, class_name: str = "", **props): - """Current page breadcrumb (non-clickable)""" - base_classes = "text-[var(--foreground)] font-normal" - - return rx.el.span( - *children, - role="link", - aria_disabled="true", - aria_current="page", - data_slot="breadcrumb-page", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def breadcrumb_separator(*children, class_name: str = "", **props): - """Separator between breadcrumb items""" - base_classes = "[&>svg]:size-3.5" - - if not children: - children = (rx.icon(tag="chevron-right", size=14),) - - return rx.el.li( - *children, - role="presentation", - aria_hidden="true", - data_slot="breadcrumb-separator", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def breadcrumb_ellipsis(class_name: str = "", **props): - """Ellipsis for collapsed breadcrumb items""" - base_classes = "flex size-9 items-center justify-center" - - return rx.el.span( - rx.icon(tag="ellipsis", size=16), - rx.el.span("More", class_name="sr-only"), - role="presentation", - aria_hidden="true", - data_slot="breadcrumb-ellipsis", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) -``` - - -# Examples - -## Basic Demo -A basic breadcrumb showing the default navigation structure. - - -```python -def breadcrumb_demo(): - return rx.el.div( - breadcrumb( - breadcrumb_list( - breadcrumb_item( - breadcrumb_link("Home", href="#"), - ), - breadcrumb_separator(), - breadcrumb_item( - rx.menu.root( - rx.menu.trigger( - rx.box( - breadcrumb_ellipsis(class_name="size-4"), - rx.el.span("Toggle menu", class_name="sr-only"), - class_name="flex items-center gap-1", - ), - ), - rx.menu.content( - rx.menu.item("Documentation"), - rx.menu.item("Themes"), - rx.menu.item("GitHub"), - class_name="min-w-[8rem]", - ), - ), - ), - breadcrumb_separator(), - breadcrumb_item( - breadcrumb_link("Components", href="#"), - ), - breadcrumb_separator(), - breadcrumb_item( - breadcrumb_page("Breadcrumb"), - ), - ), - ), - class_name="p-8", - ) -``` - - -## Simple Breadcrumb -A minimal breadcrumb with plain text links. - - -```python -def breadcrumb_simple(): - return rx.box( - breadcrumb( - breadcrumb_list( - breadcrumb_item( - breadcrumb_link("Home", href="#"), - ), - breadcrumb_separator(), - breadcrumb_item( - breadcrumb_link("Products", href="#"), - ), - breadcrumb_separator(), - breadcrumb_item( - breadcrumb_link("Electronics", href="#"), - ), - breadcrumb_separator(), - breadcrumb_item( - breadcrumb_page("Laptop"), - ), - ), - ), - class_name="p-8", - ) -``` - - -## Icon Breadcrumb -A breadcrumb that includes icons alongside link labels. - - -```python -def breadcrumb_with_icons(): - return rx.el.div( - breadcrumb( - breadcrumb_list( - breadcrumb_item( - breadcrumb_link( - rx.icon(tag="home", size=14), - "Home", - href="#", - class_name="flex flex-row gap-x-1 items-center", - ), - ), - breadcrumb_separator(), - breadcrumb_item( - breadcrumb_link( - rx.icon(tag="folder", size=14), - "Documents", - href="#", - class_name="flex flex-row gap-x-1 items-center", - ), - ), - breadcrumb_separator(), - breadcrumb_item( - breadcrumb_page( - rx.icon(tag="file-text", size=14), - "README.md", - class_name="flex flex-row gap-x-1 items-center", - ), - ), - ), - ), - class_name="p-8", - ) -``` - - -## Custom Separator -A breadcrumb with a customized separator between items. - - -```python -def breadcrumb_custom_separator(): - return rx.el.div( - breadcrumb( - breadcrumb_list( - breadcrumb_item( - breadcrumb_link("Home", href="#"), - ), - breadcrumb_separator( - rx.text("/", class_name="text-[var(--muted-foreground)]") - ), - breadcrumb_item( - breadcrumb_link("Blog", href="#"), - ), - breadcrumb_separator( - rx.text("/", class_name="text-[var(--muted-foreground)]") - ), - breadcrumb_item( - breadcrumb_page("Article"), - ), - ), - ), - class_name="p-8", - ) -``` - diff --git a/assets/docs/-components/button.md b/assets/docs/-components/button.md deleted file mode 100644 index 7be70c13..00000000 --- a/assets/docs/-components/button.md +++ /dev/null @@ -1,235 +0,0 @@ - - -# Button - -Displays a button or a component that looks like a button. - -# Installation - -Copy the following code into your app directory. - - -```python -from typing import Literal - -from reflex.components.core.cond import cond -from reflex.components.el import Button as BaseButton -from reflex.vars.base import Var - -from ..component import CoreComponent -from ...icons.others import spinner - -LiteralButtonVariant = Literal[ - "primary", "destructive", "outline", "secondary", "ghost", "link", "dark" -] -LiteralButtonSize = Literal[ - "xs", "sm", "md", "lg", "xl", "icon-xs", "icon-sm", "icon-md", "icon-lg", "icon-xl" -] - -DEFAULT_CLASS_NAME = ( - "inline-flex items-center justify-center gap-2 whitespace-nowrap " - "rounded-md text-sm font-medium transition-all " - "disabled:pointer-events-none disabled:opacity-50 outline-none " - "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 " - "[&_svg]:shrink-0 shrink-0" -) - -BUTTON_VARIANTS = { - "variant": { - "default": "bg-primary text-primary-foreground hover:bg-primary/90", - "destructive": ( - "bg-[var(--destructive)] text-white hover:bg-[var(--destructive)]/90 " - "focus-visible:ring-[var(--destructive)]/20 " - "dark:focus-visible:ring-[var(--destructive)]/40 " - "dark:bg-[var(--destructive)]/60" - ), - "outline": ( - "border border-input bg-background shadow-xs " - "hover:bg-[var(--accent)] hover:text-[var(--accent-foreground)] " - "dark:bg-[var(--input)]/30 dark:border-input " - "dark:hover:bg-[var(--input)]/50" - ), - "secondary": ("bg-secondary text-secondary-foreground hover:bg-secondary/80"), - "ghost": ( - "hover:bg-accent hover:text-accent-foreground " - "dark:hover:bg-[var(--accent)]/50" - ), - "link": "text-primary underline-offset-4 hover:underline", - }, - "size": { - "default": "h-9 px-4 py-2 has-[>svg]:px-3", - "sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", - "lg": "h-10 rounded-md px-6 has-[>svg]:px-4", - "icon": "size-9", - "icon-sm": "size-8", - "icon-lg": "size-10", - }, -} - - -class Button(BaseButton, CoreComponent): - """A custom button component.""" - - # Button variant. Defaults to "primary". - variant: Var[LiteralButtonVariant] - - # Button size. Defaults to "md". - size: Var[LiteralButtonSize] - - # The loading state of the button - loading: Var[bool] - - @classmethod - def create(cls, *children, **props) -> BaseButton: - """Create the button component.""" - variant = props.pop("variant", "default") - cls.validate_variant(variant) - - size = props.pop("size", "default") - cls.validate_size(size) - - loading = props.pop("loading", False) - disabled = props.pop("disabled", False) - - button_classes = f"{DEFAULT_CLASS_NAME} {BUTTON_VARIANTS['variant'][variant]} {BUTTON_VARIANTS['size'][size]}" - - cls.set_class_name(button_classes, props) - - children_list = list(children) - - if isinstance(loading, Var): - props["disabled"] = cond(loading, True, disabled) - children_list.insert(0, cond(loading, spinner())) - else: - props["disabled"] = True if loading else disabled - children_list.insert(0, spinner()) if loading else None - - return super().create(*children_list, **props) - - @staticmethod - def validate_variant(variant: LiteralButtonVariant): - """Validate the button variant.""" - if variant not in BUTTON_VARIANTS["variant"]: - available_variants = ", ".join(BUTTON_VARIANTS["variant"].keys()) - message = ( - f"Invalid variant: {variant}. Available variants: {available_variants}" - ) - raise ValueError(message) - - @staticmethod - def validate_size(size: LiteralButtonSize): - """Validate the button size.""" - if size not in BUTTON_VARIANTS["size"]: - available_sizes = ", ".join(BUTTON_VARIANTS["size"].keys()) - message = f"Invalid size: {size}. Available sizes: {available_sizes}" - raise ValueError(message) - - def _exclude_props(self) -> list[str]: - return [ - *super()._exclude_props(), - "size", - "variant", - "loading", - ] - - -button = Button.create -``` - - -# Examples - -## Sizes - -Showcases buttons in different predefined sizes (default, small, large, icon, etc). - - -```python -def button_size_examples(): - return rx.el.div( - button("Small", size="sm"), - button("Default", size="default"), - button("Large", size="lg"), - class_name="flex items-center gap-3", - ) -``` - - -## Default - -The default visual style for buttons with standard background and hover effects. - - -```python -def button_default_example(): - return button("Default", variant="default") -``` - - -## Secondary - -A more muted alternative to the default button, useful for less prominent actions. - - -```python -def button_secondary_example(): - return button("Secondary", variant="secondary") -``` - - -## Outline - -Buttons with a bordered outline, blending well with minimal UIs or light themes. - - -```python -def button_outline_example(): - return button("Outline", variant="outline") -``` - - -## Ghost - -A button style with no background or border, ideal for subtle UI actions. - - -```python -def button_ghost_example(): - return button("Ghost", variant="ghost") -``` - - -## Link - -A button styled to look like a hyperlink — useful for inline actions or navigation. - - -```python -def button_link_example(): - return button("Link", variant="link") -``` - - -## Destructive - -A bold style used for destructive or dangerous actions like “Delete”. - - -```python -def button_destructive_example(): - return button("Destructive", variant="destructive") -``` - - -## Icon - -Examples showing icon-only buttons with varying sizes for compact UI elements. - - -```python -def button_icon_examples(): - return ( - button(rx.icon("mail", class_name="size-4"), variant="outline", size="icon-sm"), - ) -``` - diff --git a/assets/docs/-components/input-group.md b/assets/docs/-components/input-group.md deleted file mode 100644 index 2168e5c5..00000000 --- a/assets/docs/-components/input-group.md +++ /dev/null @@ -1,174 +0,0 @@ - - -# Input Group - -Combines inputs with prefixes, suffixes, or footers for structured data entry. - -# Installation - -Copy the following code into your app directory. - - -```python -import reflex as rx -from typing import Optional, Union - - -def input_with_addons( - *children, - placeholder: str = "", - prefix: Optional[Union[str, "rx.Component"]] = None, - suffix: Optional[Union[str, "rx.Component"]] = None, - input_type: str = "text", - class_name: str = "", - **props, -): - children = list(children) - - if prefix: - if isinstance(prefix, str): - prefix = rx.text( - prefix, - class_name="text-[var(--muted-foreground)] text-sm font-medium pl-2 select-none pointer-events-none", - ) - children.insert(0, prefix) - - children.append( - rx.el.input( - type=input_type, - placeholder=placeholder, - class_name=( - "flex-1 bg-transparent border-0 outline-none " - "text-[var(--foreground)] placeholder:text-[var(--muted-foreground)] " - "px-2 py-2 text-sm " - + ("pl-2 " if prefix else "") - + ("pr-2 " if suffix else "") - ), - **props, - ) - ) - - if suffix: - if isinstance(suffix, str): - suffix = rx.text( - suffix, - class_name="text-[var(--muted-foreground)] text-sm font-medium pr-2 select-none pointer-events-none", - ) - children.append(suffix) - - return rx.box( - *children, - class_name=( - "flex items-center w-full h-9 " - "bg-transparent border border-[var(--input)] dark:bg-[var(--input)]/30 rounded-[var(--radius)] shadow-xs " - "focus-within:border-[var(--ring)] focus-within:ring-[var(--ring)]/50 focus-within:ring-[3px] " - "transition-[color,box-shadow] " + class_name - ), - ) - - -def textarea_with_footer( - placeholder: str = "", - footer_text: Optional[str] = None, - class_name: str = "", - **props, -): - children = [ - rx.el.textarea( - placeholder=placeholder, - class_name=( - "flex-1 bg-transparent border-0 outline-none resize-none " - "text-[var(--foreground)] placeholder:text-[var(--muted-foreground)] placeholder:text-sm " - "px-3 py-3 text-sm " + ("pb-2 " if footer_text else "") - ), - **props, - ) - ] - - if footer_text: - children.append( - rx.text( - footer_text, - class_name="text-[var(--muted-foreground)] text-xs px-3 pb-3 pt-0 select-none pointer-events-none", - ) - ) - - return rx.box( - *children, - class_name=( - "flex flex-col w-full " - "bg-transparent border border-[var(--input)] dark:bg-[var(--input)]/30 rounded-[var(--radius)] shadow-xs " - "focus-within:border-[var(--ring)] focus-within:ring-[var(--ring)]/50 focus-within:ring-[3px] " - "transition-[color,box-shadow] " + class_name - ), - ) -``` - - -# Examples - -## Price Input -An input field with a currency prefix for entering prices. - - -```python -def input_price(): - return rx.el.div( - input_with_addons( - placeholder="0.00", - prefix="$", - suffix="USD", - ), - class_name="w-full max-w-sm mx-auto py-6", - ) -``` - - -## URL Input -An input field with a prefixed URL scheme for web addresses. - - -```python -def input_url(): - return rx.el.div( - input_with_addons( - placeholder="example.com", - prefix="https://", - suffix=".com", - ), - class_name="w-full max-w-sm mx-auto py-6", - ) -``` - - -## Email Input -An input field that appends a fixed domain for email entry. - - -```python -def input_email(): - return rx.el.div( - input_with_addons( - placeholder="Enter your username", - suffix="@company.com", - ), - class_name="w-full max-w-sm mx-auto py-6", - ) -``` - - -## Textarea with Footer -A multiline input with a footer displaying additional information or controls. - - -```python -def input_textarea(): - return rx.el.div( - textarea_with_footer( - placeholder="Enter your message", - footer_text="120 characters left", - ), - class_name="w-full max-w-sm mx-auto py-6", - ) -``` - diff --git a/assets/docs/-components/input.md b/assets/docs/-components/input.md deleted file mode 100644 index 348f0952..00000000 --- a/assets/docs/-components/input.md +++ /dev/null @@ -1,165 +0,0 @@ - - -# Input - -Displays a form input field or a component that looks like an input field. - -# Installation - -Copy the following code into your app directory. - - -```python -from reflex.components.el import Input -from reflex.components.component import ComponentNamespace - -INPUT = ( - "file:text-[var(--foreground)] placeholder:text-[var(--muted-foreground)] " - "selection:bg-[var(--primary)] selection:text-[var(--primary-foreground)] " - "dark:bg-[var(--input)]/30 border-[var(--input)] " - "h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs " - "transition-[color,box-shadow] outline-none " - "file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium " - "disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 " - "md:text-sm " - "focus-visible:border-[var(--ring)] focus-visible:ring-[var(--ring)]/50 focus-visible:ring-[3px] " - "aria-invalid:ring-[var(--destructive)]/20 dark:aria-invalid:ring-[var(--destructive)]/40 " - "aria-invalid:border-[var(--destructive)]" -) - - -class InputComponent(Input): - """Styled input component that extends rx.el.input.""" - - @classmethod - def create(cls, *children, **props): - """Create the input component with default styling.""" - # Get existing class_name or empty string - existing_class = props.get("class_name", "") - - # Merge base classes with any custom classes - props["class_name"] = f"{INPUT} {existing_class}".strip() - - # Set data slot - props["data_slot"] = "input" - - # Set default type if not provided - if "type" not in props: - props["type"] = "text" - - return super().create(*children, **props) - - -class Input(ComponentNamespace): - """Namespace for Input component.""" - - __call__ = staticmethod(InputComponent.create) - - -input = Input() -``` - - -# Examples - -## Basic Demo -A simple text input demonstrating the default appearance and behavior. - - -```python -def input_demo(): - return rx.el.div( - rx.text("Text Input", class_name="text-sm font-medium mb-2"), - input( - type="text", - placeholder="Enter your name", - ), - class_name="w-full max-w-md p-8", - ) -``` - - -## Email -An input field optimized for email address entry. - - -```python -def input_email(): - return rx.el.div( - input_with_addons( - placeholder="Enter your username", - suffix="@company.com", - ), - class_name="w-full max-w-sm mx-auto py-6", - ) -``` - - -## Password -An input field that hides characters for secure password entry. - - -```python -def input_password(): - return rx.el.div( - rx.el.p("Password Input", class_name="text-sm font-medium mb-2"), - input( - type="password", - placeholder="Enter your password", - ), - class_name="w-full max-w-md p-8", - ) -``` - - -## Disabled -An example of an input field in a disabled state. - - -```python -def input_disabled(): - return rx.el.div( - rx.el.p("Disabled Input", class_name="text-sm font-medium mb-2"), - input( - type="text", - placeholder="Disabled input", - disabled=True, - ), - class_name="w-full max-w-md p-8", - ) -``` - - -## File Input -An input field for selecting and uploading files. - - -```python -def input_file(): - return rx.el.div( - rx.el.p("File Input", class_name="text-sm font-medium mb-2"), - input( - type="file", - ), - class_name="w-full max-w-md p-8", - ) -``` - - -## Custom Input -An input field with a custom width and styling. - - -```python -def input_custom_width(): - return rx.el.div( - rx.el.p("Custom Width", class_name="text-sm font-medium mb-2"), - input( - type="text", - placeholder="Max width 300px", - class_name="max-w-[300px]", - ), - class_name="w-full max-w-md p-8", - ) -``` - diff --git a/assets/docs/-components/kbd.md b/assets/docs/-components/kbd.md deleted file mode 100644 index b8aa11a7..00000000 --- a/assets/docs/-components/kbd.md +++ /dev/null @@ -1,234 +0,0 @@ - - -# Kbd - -Display keyboard keys and shortcuts with proper styling. - -# Installation - -Copy the following code into your app directory. - - -```python -import reflex as rx - - -def kbd(*children, class_name: str = "", **props): - """ - Keyboard key component matching shadcn/ui styling. - Uses CSS variables from your theme for colors. - - Args: - *children: Key content (text, symbols, icons) - class_name: Additional classes - **props: Additional props for the kbd element - """ - base_classes = ( - "bg-[var(--muted)] text-[var(--muted-foreground)] " - "pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 " - "rounded-sm px-1 font-sans text-xs font-medium select-none " - "[&_svg:not([class*='size-'])]:size-3 " - "[[data-slot=tooltip-content]_&]:bg-[var(--background)]/20 " - "[[data-slot=tooltip-content]_&]:text-[var(--background)] " - "dark:[[data-slot=tooltip-content]_&]:bg-[var(--background)]/10" - ) - - return rx.el.kbd( - *children, - data_slot="kbd", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def kbd_group(*children, class_name: str = "", **props): - """ - Group multiple kbd elements together with spacing. - - Args: - *children: Multiple kbd elements - class_name: Additional classes - **props: Additional props for the group element - """ - base_classes = "inline-flex items-center gap-1" - - return rx.el.kbd( - *children, - data_slot="kbd-group", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) -``` - - -# Examples - -## Default -A basic example showing a single styled keyboard key. - - -```python -def kbd_demo(): - """ - Example matching the shadcn KbdDemo component. - Shows keyboard shortcuts with modifier keys. - """ - return rx.box( - # Mac modifier keys - kbd_group( - kbd("⌘"), - kbd("⇧"), - kbd("⌥"), - kbd("⌃"), - ), - # Keyboard shortcut combination - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("B"), - ), - class_name="flex flex-col items-center gap-4 p-8", - ) -``` - - -## Common Shortcuts -Displays familiar keyboard shortcuts like copy or paste. - - -```python -def kbd_shortcuts(): - """Common keyboard shortcuts""" - return rx.box( - rx.box( - rx.text("Save:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("S"), - ), - class_name="flex items-center", - ), - rx.box( - rx.text("Copy:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("C"), - ), - class_name="flex items-center", - ), - rx.box( - rx.text("Paste:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("V"), - ), - class_name="flex items-center", - ), - rx.box( - rx.text("Undo:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("Z"), - ), - class_name="flex items-center", - ), - class_name="flex flex-col gap-3 p-8", - ) -``` - - -## Special Keys -Shows styling for special keys such as Enter, Tab, or Esc. - - -```python -def kbd_special_keys(): - """Special key examples""" - return rx.box( - kbd("Enter"), - kbd("Esc"), - kbd("Tab"), - kbd("Space"), - kbd("←"), - kbd("→"), - kbd("↑"), - kbd("↓"), - kbd("Delete"), - kbd("Backspace"), - class_name="flex flex-wrap gap-2 p-8", - ) -``` - - -## Complex Shortcuts -Demonstrates multi-key combinations for advanced shortcuts. - - -```python -def kbd_complex_shortcuts(): - """Complex multi-key shortcuts""" - return rx.box( - # Three modifier keys - rx.box( - rx.text("Screenshot:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("Ctrl"), - rx.el.span("+"), - kbd("Shift"), - rx.el.span("+"), - kbd("S"), - ), - class_name="flex items-center mb-3", - ), - # Mac command - rx.box( - rx.text("Quit:", class_name="text-sm font-medium mr-2"), - kbd_group( - kbd("⌘"), - rx.el.span("+"), - kbd("Q"), - ), - class_name="flex items-center mb-3", - ), - # Function key - rx.box( - rx.text("Full Screen:", class_name="text-sm font-medium mr-2"), - kbd("F11"), - class_name="flex items-center", - ), - class_name="p-8", - ) -``` - - -## With Icons -Displays keyboard shortcuts paired with icons for clarity. - - -```python -def kbd_with_icons(): - """Kbd with icons""" - return rx.box( - kbd_group( - kbd( - rx.icon(tag="command", size=12), - ), - rx.el.span("+"), - kbd("K"), - ), - kbd_group( - kbd( - rx.icon(tag="arrow-left", size=12), - ), - kbd( - rx.icon(tag="arrow-right", size=12), - ), - ), - class_name="flex flex-col items-center gap-4 p-8", - ) -``` - diff --git a/assets/docs/-components/menu.md b/assets/docs/-components/menu.md deleted file mode 100644 index 1cfba0d8..00000000 --- a/assets/docs/-components/menu.md +++ /dev/null @@ -1,221 +0,0 @@ - - -# Dropdown Menu - -Displays a menu to the user — such as a set of actions or functions — triggered by a button. - -# Installation - -Copy the following code into your app directory. - - -```python -import reflex as rx -from typing import Literal - - -def dropdown_menu_root(*children, **props): - """Root dropdown menu container""" - return rx.menu.root(*children, data_slot="dropdown-menu", **props) - - -def dropdown_menu_trigger(*children, **props): - """Trigger button for the dropdown menu""" - return rx.menu.trigger(*children, data_slot="dropdown-menu-trigger", **props) - - -def dropdown_menu_content(*children, class_name: str = "", **props): - """ - Dropdown menu content container. - Uses CSS variables from your shadcn theme. - """ - base_classes = ( - "bg--popover text-popover-foreground " - "data-[state=open]:animate-in data-[state=closed]:animate-out " - "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 " - "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 " - "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 " - "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 " - "z-50 min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border border-input dark:border-[var(--input)] p-1 shadow-md" - ) - - return rx.menu.content( - *children, - data_slot="dropdown-menu-content", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def dropdown_menu_item( - *children, - variant: Literal["default", "destructive"] = "default", - inset: bool = False, - disabled: bool = False, - class_name: str = "", - **props, -): - """Dropdown menu item with optional variant styling""" - base_classes = ( - "focus:bg-[var(--accent)] focus:text-[var(--accent-foreground)] " - "data-[variant=destructive]:text-[var(--destructive)] " - "data-[variant=destructive]:focus:bg-[var(--destructive)]/10 " - "dark:data-[variant=destructive]:focus:bg-[var(--destructive)]/20 " - "data-[variant=destructive]:focus:text-[var(--destructive)] " - "[&_svg:not([class*='text-'])]:text-[var(--muted-foreground)] " - "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm " - "outline-none select-none " - "data-[disabled]:pointer-events-none data-[disabled]:opacity-50 " - + ("pl-8 " if inset else "") - + "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" - ) - - return rx.menu.item( - *children, - data_slot="dropdown-menu-item", - data_variant=variant, - data_inset=inset, - disabled=disabled, - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def dropdown_menu_label(*children, inset: bool = False, class_name: str = "", **props): - """Label/header for menu sections""" - base_classes = "px-2 py-1.5 text-sm font-medium " + ("pl-8" if inset else "") - - return rx.el.div( - *children, - data_slot="dropdown-menu-label", - data_inset=inset, - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def dropdown_menu_separator(class_name: str = "", **props): - """Separator line between menu items""" - base_classes = "bg-input -mx-1 my-1 h-px" - - return rx.menu.separator( - data_slot="dropdown-menu-separator", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def dropdown_menu_shortcut(*children, class_name: str = "", **props): - """Keyboard shortcut display""" - base_classes = "text-[var(--muted-foreground)] ml-auto text-xs tracking-widest" - - return rx.el.span( - *children, - data_slot="dropdown-menu-shortcut", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def dropdown_menu_sub_trigger( - *children, inset: bool = False, class_name: str = "", **props -): - """Trigger for submenu""" - base_classes = ( - "focus:bg-[var(--accent)] focus:text-[var(--accent-foreground)] " - "data-[state=open]:bg-[var(--accent)] data-[state=open]:text-[var(--accent-foreground)] " - "[&_svg:not([class*='text-'])]:text-[var(--muted-foreground)] " - "flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none " - + ("pl-8 " if inset else "") - + "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" - ) - - return rx.menu.sub_trigger( - *children, - data_slot="dropdown-menu-sub-trigger", - data_inset=inset, - class_name=f"[&_.rt-BaseMenuSubTriggerIcon.rt-DropdownMenuSubtriggerIcon]:!size-2 [&_.rt-BaseMenuSubTriggerIcon.rt-DropdownMenuSubtriggerIcon]:!shrink-0 {base_classes} {class_name}".strip(), - **props, - ) - - -def dropdown_menu_sub_content(*children, class_name: str = "", **props): - """Submenu content container""" - base_classes = ( - "bg-[var(--popover)] text-[var(--popover-foreground)] " - "data-[state=open]:animate-in data-[state=closed]:animate-out " - "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 " - "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 " - "z-50 min-w-[8rem] overflow-hidden rounded-md border border-input p-1 shadow-lg border dark:border-[var(--input)]" - ) - - return rx.menu.sub_content( - *children, - data_slot="dropdown-menu-sub-content", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) -``` - - -# Example -A basic dropdown menu that opens when the user clicks a trigger button. - - -```python -def dropdown_menu_demo(): - return rx.el.div( - dropdown_menu_root( - dropdown_menu_trigger( - button("Click Me!", variant="outline"), - ), - dropdown_menu_content( - dropdown_menu_label("My Account"), - dropdown_menu_item( - "Profile", - dropdown_menu_shortcut("⇧⌘P"), - ), - dropdown_menu_item( - "Billing", - dropdown_menu_shortcut("⌘B"), - ), - dropdown_menu_item( - "Settings", - dropdown_menu_shortcut("⌘S"), - ), - dropdown_menu_item( - "Keyboard shortcuts", - dropdown_menu_shortcut("⌘K"), - ), - dropdown_menu_separator(), - dropdown_menu_item("Team"), - rx.menu.sub( - dropdown_menu_sub_trigger("Invite users"), - dropdown_menu_sub_content( - dropdown_menu_item("Email"), - dropdown_menu_item("Message"), - dropdown_menu_separator(), - dropdown_menu_item("More..."), - ), - ), - dropdown_menu_item( - "New Team", - dropdown_menu_shortcut("⌘+T"), - ), - dropdown_menu_separator(), - dropdown_menu_item("GitHub"), - dropdown_menu_item("Support"), - dropdown_menu_item("API", disabled=True), - dropdown_menu_separator(), - dropdown_menu_item( - "Log out", - dropdown_menu_shortcut("⇧⌘Q"), - ), - class_name="w-56", - size="1", - ), - ), - class_name="p-8", - ) -``` - diff --git a/assets/docs/-components/popover.md b/assets/docs/-components/popover.md deleted file mode 100644 index efc4640c..00000000 --- a/assets/docs/-components/popover.md +++ /dev/null @@ -1,181 +0,0 @@ - - -# Popover - -Displays rich content in a portal, triggered by a button. - -# Installation - -Copy the following code into your app directory. - - -```python -import reflex as rx - - -def popover_root(*children, **props): - """ - Root popover container. - Uses Reflex's built-in popover component. - """ - return rx.popover.root(*children, data_slot="popover", **props) - - -def popover_trigger(*children, **props): - """Trigger element for the popover""" - return rx.popover.trigger(*children, data_slot="popover-trigger", **props) - - -def popover_content(*children, class_name: str = "", **props): - """ - Popover content container. - Uses CSS variables from your shadcn theme. - """ - base_classes = ( - "bg-[var(--popover)] text-[var(--popover-foreground)] " - "data-[state=open]:animate-in data-[state=closed]:animate-out " - "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 " - "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 " - "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 " - "data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 " - "z-50 w-72 rounded-md border border-input dark:border-[var(--input)] p-4 shadow-md outline-none" - ) - - return rx.popover.content( - *children, - data_slot="popover-content", - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) -``` - - -# Example -A basic popover that appears when the user clicks the trigger button. - - -```python -def popover_demo(): - return rx.el.div( - popover_root( - popover_trigger(button("Click Me", variant="outline")), - popover_content( - rx.el.div( - # Header section - rx.el.div( - rx.el.h4( - "Dimensions", - class_name="leading-none font-medium", - ), - rx.el.p( - "Set the dimensions for the layer.", - class_name="text-[var(--muted-foreground)] text-sm", - ), - class_name="space-y-2", - ), - # Input fields section - rx.el.div( - # Width input - rx.el.div( - rx.el.label( - "Width", - html_for="width", - class_name="text-sm font-medium", - ), - rx.el.input( - type="text", - id="width", - value="100%", - placeholder="100%", - class_name=( - "col-span-2 h-8 w-full rounded-md border border-[var(--input)] " - "bg-transparent px-3 py-1 text-sm shadow-xs " - "transition-[color,box-shadow] outline-none " - "placeholder:text-[var(--muted-foreground)] " - "focus-visible:border-[var(--ring)] focus-visible:ring-[var(--ring)]/50 focus-visible:ring-[3px] " - "dark:bg-[var(--input)]/30" - ), - ), - class_name="grid grid-cols-3 items-center gap-4", - ), - # Max width input - rx.el.div( - rx.el.label( - "Max. width", - html_for="maxWidth", - class_name="text-sm font-medium", - ), - rx.el.input( - type="text", - id="maxWidth", - value="300px", - placeholder="300px", - class_name=( - "col-span-2 h-8 w-full rounded-md border border-[var(--input)] " - "bg-transparent px-3 py-1 text-sm shadow-xs " - "transition-[color,box-shadow] outline-none " - "placeholder:text-[var(--muted-foreground)] " - "focus-visible:border-[var(--ring)] focus-visible:ring-[var(--ring)]/50 focus-visible:ring-[3px] " - "dark:bg-[var(--input)]/30" - ), - ), - class_name="grid grid-cols-3 items-center gap-4", - ), - # Height input - rx.el.div( - rx.el.label( - "Height", - html_for="height", - class_name="text-sm font-medium", - ), - rx.el.input( - type="text", - id="height", - value="25px", - placeholder="25px", - class_name=( - "col-span-2 h-8 w-full rounded-md border border-[var(--input)] " - "bg-transparent px-3 py-1 text-sm shadow-xs " - "transition-[color,box-shadow] outline-none " - "placeholder:text-[var(--muted-foreground)] " - "focus-visible:border-[var(--ring)] focus-visible:ring-[var(--ring)]/50 focus-visible:ring-[3px] " - "dark:bg-[var(--input)]/30" - ), - ), - class_name="grid grid-cols-3 items-center gap-4", - ), - # Max height input - rx.el.div( - rx.el.label( - "Max. height", - html_for="maxHeight", - class_name="text-sm font-medium", - ), - rx.el.input( - type="text", - id="maxHeight", - value="none", - placeholder="none", - class_name=( - "col-span-2 h-8 w-full rounded-md border border-[var(--input)] " - "bg-transparent px-3 py-1 text-sm shadow-xs " - "transition-[color,box-shadow] outline-none " - "placeholder:text-[var(--muted-foreground)] " - "focus-visible:border-[var(--ring)] focus-visible:ring-[var(--ring)]/50 focus-visible:ring-[3px] " - "dark:bg-[var(--input)]/30" - ), - ), - class_name="grid grid-cols-3 items-center gap-4", - ), - class_name="grid gap-2", - ), - class_name="grid gap-4", - ), - class_name="w-80", - side="top", - ), - ), - class_name="p-8", - ) -``` - diff --git a/assets/docs/-components/text-area.md b/assets/docs/-components/text-area.md deleted file mode 100644 index e07fd186..00000000 --- a/assets/docs/-components/text-area.md +++ /dev/null @@ -1,101 +0,0 @@ - - -# Text Area - -Displays a form textarea or a component that looks like a textarea. - -# Installation - -Copy the following code into your app directory. - - -```python -"""Custom Textarea component.""" - -from reflex.components.component import Component -from reflex.components.el import Textarea as TextareaComponent - -from ..component import CoreComponent - - -class ClassNames: - """Class names for textarea components.""" - - ROOT = "focus:shadow-[0px_0px_0px_2px_var(--primary-4)] focus:border-primary-7 focus:hover:border-primary-7 bg-secondary-1 border border-secondary-a4 hover:border-secondary-a6 transition-[color,box-shadow] disabled:border-secondary-4 disabled:bg-secondary-3 disabled:text-secondary-8 disabled:cursor-not-allowed cursor-text min-h-24 rounded-ui-md text-secondary-12 placeholder:text-secondary-9 text-sm disabled:placeholder:text-secondary-8 w-full outline-none max-h-[15rem] resize-none overflow-y-auto px-3 py-2.5 font-medium" - - -class Textarea(TextareaComponent, CoreComponent): - """Root component for Textarea.""" - - @classmethod - def create(cls, *children, **props) -> Component: - """Create the textarea component.""" - props.setdefault( - "custom_attrs", - { - "autoComplete": "off", - "autoCapitalize": "none", - "autoCorrect": "off", - "spellCheck": "false", - }, - ) - props["data-slot"] = "textarea" - cls.set_class_name(ClassNames.ROOT, props) - return super().create(*children, **props) - - -textarea = Textarea.create -``` - - -# Examples - -## Basic Demo -A standard multiline text area for general text input. - - -```python -def textarea_demo(): - return rx.el.div( - rx.el.p("Textarea", class_name="text-sm font-medium mb-2"), - textarea( - placeholder="Enter your message here...", - ), - class_name="w-full max-w-md p-8", - ) -``` - - -## Disabled -A text area shown in a disabled, non-editable state. - - -```python -def textarea_disabled(): - return rx.el.div( - rx.el.p("Disabled Textarea", class_name="text-sm font-medium mb-2"), - textarea( - placeholder="This is disabled", - disabled=True, - ), - class_name="w-full max-w-md p-8", - ) -``` - - -## Custom Text Area -A text area with custom styling or dimensions. - - -```python -def textarea_custom(): - return rx.el.div( - rx.el.p("Custom Height", class_name="text-sm font-medium mb-2"), - textarea( - placeholder="Taller textarea", - class_name="min-h-32", - ), - class_name="w-full max-w-md p-8", - ) -``` - diff --git a/assets/docs/-components/typography.md b/assets/docs/-components/typography.md deleted file mode 100644 index 4d07639c..00000000 --- a/assets/docs/-components/typography.md +++ /dev/null @@ -1,386 +0,0 @@ - - -# Typography - -Styles for headings, paragraphs, lists, and other text elements using Tailwind utility classes. - -# Installation - -Copy the following code into your app directory. - - -```python -import reflex as rx - - -def typography_h1(*children, class_name: str = "", **props): - """Large heading - h1""" - base_classes = "scroll-m-20 text-4xl font-extrabold tracking-tight text-balance" - return rx.el.h1( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_h2(*children, class_name: str = "", **props): - """Section heading - h2""" - base_classes = "scroll-m-20 border-b border-input pb-2 text-3xl font-semibold tracking-tight first:mt-0" - return rx.el.h2( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_h3(*children, class_name: str = "", **props): - """Subsection heading - h3""" - base_classes = "scroll-m-20 text-2xl font-semibold tracking-tight" - return rx.el.h3( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_h4(*children, class_name: str = "", **props): - """Small heading - h4""" - base_classes = "scroll-m-20 text-xl font-semibold tracking-tight" - return rx.el.h4( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_p(*children, class_name: str = "", **props): - """Paragraph text""" - base_classes = "leading-7 [&:not(:first-child)]:mt-6" - return rx.el.p( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_blockquote(*children, class_name: str = "", **props): - """Blockquote for quotes""" - base_classes = "mt-6 border-l-2 pl-6 italic" - return rx.el.blockquote( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_list(*children, class_name: str = "", **props): - """Unordered list""" - base_classes = "my-6 ml-6 list-disc [&>li]:mt-2" - return rx.el.ul( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_inline_code(*children, class_name: str = "", **props): - """Inline code""" - base_classes = "bg-[var(--muted)] relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold" - return rx.el.code( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_lead(*children, class_name: str = "", **props): - """Lead paragraph (larger intro text)""" - base_classes = "text-[var(--muted-foreground)] text-xl" - return rx.el.p( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_large(*children, class_name: str = "", **props): - """Large text""" - base_classes = "text-lg font-semibold" - return rx.el.div( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_small(*children, class_name: str = "", **props): - """Small text""" - base_classes = "text-sm leading-none font-medium" - return rx.el.small( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_muted(*children, class_name: str = "", **props): - """Muted text""" - base_classes = "text-[var(--muted-foreground)] text-sm" - return rx.el.p( - *children, class_name=f"{base_classes} {class_name}".strip(), **props - ) - - -def typography_table(*children, class_name: str = "", **props): - """Table wrapper with styling""" - base_classes = "my-6 w-full overflow-y-auto" - return rx.el.div( - rx.el.table( - *children, - class_name="w-full", - ), - class_name=f"{base_classes} {class_name}".strip(), - **props, - ) - - -def typography_table_header(*children, **props): - """Table header row""" - return rx.el.tr( - *children, - class_name="even:bg-[var(--muted)] m-0 border-t border-input p-0", - **props, - ) - - -def typography_table_head(*children, **props): - """Table header cell""" - return rx.el.th( - *children, - class_name="border border-input px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right", - **props, - ) - - -def typography_table_row(*children, **props): - """Table body row""" - return rx.el.tr( - *children, - class_name="even:bg-[var(--muted)] m-0 border-t border-input p-0", - **props, - ) - - -def typography_table_cell(*children, **props): - """Table body cell""" - return rx.el.td( - *children, - class_name="border border-input px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right", - **props, - ) -``` - - -# Examples - -## All Styles -A showcase of all typography styles in one example. - - -```python -def typography_demo(): - """Complete typography demo with Robin Hood theme""" - return rx.box( - typography_h1("The Legend of Sherwood Forest"), - typography_p( - "In the days of old England, when King Richard was away at the Crusades, a corrupt Sheriff ruled Nottingham with an iron fist. The poor were taxed beyond measure, while nobles lived in luxury.", - class_name="text-[var(--muted-foreground)] text-xl leading-7", - ), - typography_h2("The Outlaw Emerges", class_name="mt-10"), - typography_p( - "One man refused to bow to tyranny. Robin of Locksley, stripped of his lands and title, fled to ", - rx.link( - "Sherwood Forest", - href="#", - class_name="text-[var(--primary)] font-medium underline underline-offset-4", - ), - " where he gathered a band of outlaws dedicated to justice.", - ), - typography_blockquote( - '"We steal from the rich and give to the needy," he declared, "for no law is just when it starves the innocent."' - ), - typography_h3("The Merry Men", class_name="mt-8"), - typography_p( - "Robin's band grew, attracting the bravest and most skilled fighters in the realm:" - ), - typography_list( - rx.el.li("Little John: The mighty quarterstaff warrior"), - rx.el.li("Friar Tuck: The jovial man of God"), - rx.el.li("Maid Marian: The fearless noblewoman"), - ), - typography_p( - "Together, they became legends, robbing tax collectors and corrupt nobles, always staying one step ahead of the Sheriff's men." - ), - typography_h3("The Sheriff's Fury", class_name="mt-8"), - typography_p( - "The Sheriff of Nottingham grew desperate. Every convoy he sent through Sherwood was ambushed, every cruel tax returned to the poor. His reputation crumbled while Robin's legend grew." - ), - typography_h3("The Great Archery Contest", class_name="mt-8"), - typography_p( - "In a cunning trap, the Sheriff announced an archery contest with a golden arrow as prize. Robin couldn't resist the challenge." - ), - typography_table( - rx.el.thead( - typography_table_header( - typography_table_head("Sheriff's Treasury"), - typography_table_head("Common Folk's Wellbeing"), - ), - ), - rx.el.tbody( - typography_table_row( - typography_table_cell("Overflowing"), - typography_table_cell("Starving"), - ), - typography_table_row( - typography_table_cell("Modest"), - typography_table_cell("Surviving"), - ), - typography_table_row( - typography_table_cell("Empty"), - typography_table_cell("Thriving"), - ), - ), - ), - typography_p( - "Though disguised, Robin's skill gave him away. But with the help of his Merry Men and the cheering crowd, he escaped. When King Richard returned, he pardoned Robin and restored his lands." - ), - typography_p( - "The legend reminds us: when laws serve only the powerful, it falls to the brave to stand for what is right." - ), - class_name="p-8 max-w-3xl", - ) -``` - - -## H1 -An example of a large primary heading style. - - -```python -def typography_h1_example(): - """H1 example""" - return rx.el.div( - typography_h1("The Legend of Sherwood Forest"), - class_name="text-center", - ) -``` - - -## H2 -A secondary heading with slightly smaller text. - - -```python -def typography_h2_example(): - """H2 example""" - return typography_h2("The Outlaw and His Merry Men") -``` - - -## H3 -A tertiary heading for subsection titles. - - -```python -def typography_h3_example(): - """H3 example""" - return typography_h3("A Robin Hood Tale") -``` - - -## H4 -A small heading used for minor sections or labels. - - -```python -def typography_h4_example(): - """H4 example""" - return typography_h4("The people rallied to his cause") -``` - - -## Paragraph -A standard paragraph text style for body content. - - -```python -def typography_p_example(): - """Paragraph example""" - return typography_p( - "Robin Hood, seeing the suffering of the common folk, vowed to take from the rich and give to the poor until justice was restored." - ) -``` - - -## Blockquote -A styled blockquote element for quoted text. - - -```python -def typography_blockquote_example(): - """Blockquote example""" - return typography_blockquote( - '"We steal from the rich and give to the needy," he declared, "for no law is just when it starves the innocent."' - ) -``` - - -## List -A demonstration of styled ordered and unordered lists. - - -```python -def typography_list_example(): - """List example""" - return typography_list( - rx.el.li("Little John: The mighty quarterstaff warrior"), - rx.el.li("Friar Tuck: The jovial man of God"), - rx.el.li("Maid Marian: The fearless noblewoman"), - ) -``` - - -## Inline Code -Inline code styling for short snippets within text. - - -```python -def typography_inline_code_example(): - """Inline code example""" - return typography_inline_code("@buridan-ui/ui") -``` - - -## Lead -A larger, attention-grabbing paragraph for introductions. - - -```python -def typography_lead_example(): - """Lead paragraph example""" - return typography_lead( - "A legendary tale of heroism and justice, where one man stood against tyranny to defend the oppressed." - ) -``` - - -## Large -An example of text with a slightly larger font size. - - -```python -def typography_large_example(): - """Large text example""" - return typography_large("Will you join our band?") -``` - - -## Small -A smaller, subdued text style for fine print or notes. - - -```python -def typography_small_example(): - """Small text example""" - return typography_small("Sherwood Forest, England") -``` - - -## Muted -A muted text style with reduced contrast for secondary information. - - -```python -def typography_muted_example(): - """Muted text example""" - return typography_muted("In the reign of King Richard.") -``` - diff --git a/assets/docs/charts/area-chart.md b/assets/docs/charts/area-chart.md index cc354a7b..2efe412e 100644 --- a/assets/docs/charts/area-chart.md +++ b/assets/docs/charts/area-chart.md @@ -5,75 +5,121 @@ Area Charts are ideal for showing changes over time or the magnitude of multiple datasets stacked together. They combine the smoothness of line charts with the visual impact of filled areas. # Usage -Copy the following helper functions into your Reflex application. +The chart tooltip components are available in the `base_ui` library. ```python -import reflex as rx - -tooltip = { - "is_animation_active": False, - "separator": "", - "cursor": False, - "item_style": { - "color": "currentColor", - "display": "flex", - "paddingBottom": "0px", - "justifyContent": "space-between", - "textTransform": "capitalize", - }, - "label_style": { - "color": rx.color("slate", 10), - "fontWeight": "500", - }, - "content_style": { - "background": rx.color_mode_cond("oklch(0.97 0.00 0)", "oklch(0.14 0.00 286)"), - "borderColor": rx.color("slate", 5), - "borderRadius": "5px", - "fontFamily": "IBM Plex Mono,ui-monospace,monospace", - "fontSize": "0.875rem", - "lineHeight": "1.25rem", - "fontWeight": "500", - "letterSpacing": "-0.01rem", - "minWidth": "8rem", - "width": "175px", - "padding": "0.375rem 0.625rem ", - "position": "relative", - }, -} - - -def info(title: str, size: str, subtitle: str, align: str): - return rx.vstack( - rx.heading(title, size=size, weight="bold"), - rx.text(subtitle, size="1", color=rx.color("slate", 11), weight="medium"), - spacing="1", - align=align, - ) - - -def get_tooltip(): - """Standard tooltip for all charts.""" - return rx.recharts.graphing_tooltip(**tooltip) +from typing import Literal +import reflex as rx -def get_cartesian_grid(): - """Standard cartesian grid for charts.""" - return rx.recharts.cartesian_grid( - horizontal=True, vertical=False, class_name="opacity-25" - ) +Display = Literal["show", "hide"] +Swatch = Literal["square", "line", "border"] + + +def _deep_merge(base: dict, override: dict) -> dict: + result = base.copy() + for key, value in override.items(): + if key in result and isinstance(result[key], dict) and isinstance(value, dict): + result[key] = _deep_merge(result[key], value) + else: + result[key] = value + return result + + +class _ChartTooltip: + def __call__( + self, + label: Display = "show", + is_animation_active: bool = False, + separator: str = "", + cursor: bool = False, + item_style: dict = {}, + label_style: dict = {}, + content_style: dict = {}, + ) -> rx.Component: + defaults = { + "is_animation_active": is_animation_active, + "separator": separator, + "cursor": cursor, + "item_style": _deep_merge( + { + "color": "currentColor", + "display": "flex", + "paddingBottom": "0px", + "justifyContent": "space-between", + "textTransform": "capitalize", + }, + item_style, + ), + "label_style": _deep_merge( + { + "display": "none" if label == "hide" else "flex", + "fontWeight": "500", + }, + label_style, + ), + "content_style": _deep_merge( + { + "background": "var(--background)", + "borderColor": "var(--input)", + "borderRadius": "0.85rem", + "padding": "0.25rem 0.65rem", + "position": "relative", + }, + content_style, + ), + } + return rx.recharts.graphing_tooltip(**defaults) + + +class _ChartTooltipContent: + def __call__(self, num_series: int, swatch: Swatch = "square") -> str: + base = """ + [&_.recharts-tooltip-item-name]:!text-muted-foreground + [&_.recharts-tooltip-item-separator]:!w-full + [&_.recharts-tooltip-item]:!w-[8rem] + [&_.recharts-tooltip-item]:!flex + [&_.recharts-tooltip-item]:!items-center + [&_.recharts-tooltip-item]:!gap-2 + """ + ( + """ + [&_.recharts-tooltip-label]:!border-l-3 + [&_.recharts-tooltip-label]:!border-[var(--chart-1)] + [&_.recharts-tooltip-label]:!pl-2 + [&_.recharts-tooltip-label]:!py-0 + """ + if swatch == "border" + else "" + ) -def get_x_axis(data_key: str): - """Standard X axis configuration.""" - return rx.recharts.x_axis( - data_key=data_key, - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", - ) + lines = [] + for i in range(1, num_series + 1): + if swatch == "border": + lines.append(f""" + [&_.recharts-default-tooltip]:!py-2 !flex !flex-col !gap-y-0 + [&_.recharts-tooltip-item:nth-child({i})]:!border-l-3 + [&_.recharts-tooltip-item:nth-child({i})]:!border-[var(--chart-{i})] + [&_.recharts-tooltip-item:nth-child({i})]:!pl-2 + [&_.recharts-tooltip-item:nth-child({i})]:!py-0 + """) + else: + lines.append(f""" + [&_.recharts-tooltip-item:nth-child({i})]:before:!content-[''] + [&_.recharts-tooltip-item:nth-child({i})]:before:{"!w-3" if swatch == "square" else "!w-8"} + {"[&_.recharts-tooltip-item:nth-child(" + str(i) + ")]:before:!flex-shrink-0" if swatch == "square" else ""} + [&_.recharts-tooltip-item:nth-child({i})]:before:!h-3 + [&_.recharts-tooltip-item:nth-child({i})]:before:!rounded-sm + [&_.recharts-tooltip-item:nth-child({i})]:before:!bg-[var(--chart-{i})] + [&_.recharts-tooltip-item:nth-child({i})]:before:!block + """) + + return base + "\n".join(lines) + + +chart_tooltip = _ChartTooltip() +chart_tooltip_content = _ChartTooltipContent() ``` @@ -85,43 +131,57 @@ A minimal example showing a single series with a smooth gradient fill. ```python def areachart_v1(): - data = [ - {"month": "Jan", "desktop": 186}, - {"month": "Feb", "desktop": 305}, - {"month": "Mar", "desktop": 237}, - {"month": "Apr", "desktop": 73}, - {"month": "May", "desktop": 209}, - {"month": "Jun", "desktop": 214}, - ] - return rx.box( - info( - "Area Chart", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Area Chart"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.area_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.area( - data_key="desktop", - fill="var(--chart-1)", - stroke="var(--chart-1)", - stroke_width=2, + card.content( + rx.recharts.area_chart( + chart_tooltip(label="show"), + rx.recharts.cartesian_grid( + horizontal=True, + vertical=False, + class_name="opacity-30", + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, ), - get_x_axis("month"), - data=data, - width="100%", - height=250, ), - info( - "Trending up by 5.2% this month", - "2", - "January - June 2024", - "start", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + class_name=chart_tooltip_content(1, "border") + " w-full p-0", ) ``` @@ -131,44 +191,56 @@ Displays data using straight line segments between points. ```python def areachart_v2(): - data = [ - {"month": "Jan", "desktop": 186}, - {"month": "Feb", "desktop": 305}, - {"month": "Mar", "desktop": 237}, - {"month": "Apr", "desktop": 73}, - {"month": "May", "desktop": 209}, - {"month": "Jun", "desktop": 214}, - ] - return rx.box( - info( - "Area Chart - Linear", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Area Chart - Linear"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.area_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.area( - data_key="desktop", - fill="var(--chart-1)", - stroke="var(--chart-1)", - stroke_width=2, - type_="linear", + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + type_="linear", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, ), - get_x_axis("month"), - data=data, - width="100%", - height=250, ), - info( - "Trending up by 5.2% this month", - "2", - "January - June 2024", - "start", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -178,39 +250,56 @@ Renders the chart with stepped transitions, ideal for discrete intervals. ```python def areachart_v3(): - data = [ - {"month": "Jan", "desktop": 186}, - {"month": "Feb", "desktop": 305}, - {"month": "Mar", "desktop": 237}, - {"month": "Apr", "desktop": 73}, - {"month": "May", "desktop": 209}, - {"month": "Jun", "desktop": 214}, - ] - return rx.box( - info( - "Area Chart - Step", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Area Chart - Step"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.area_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.area( - data_key="desktop", - fill="var(--chart-1)", - stroke="var(--chart-1)", - stroke_width=2, - type_="step", + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + type_="step", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, ), - get_x_axis("month"), - data=data, - width="100%", - height=250, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -220,46 +309,64 @@ Visualizes multiple data series stacked on top of each other for cumulative comp ```python def areachart_v4(): - data = [ - {"month": "Jan", "desktop": 186, "mobile": 80}, - {"month": "Feb", "desktop": 305, "mobile": 200}, - {"month": "Mar", "desktop": 237, "mobile": 120}, - {"month": "Apr", "desktop": 73, "mobile": 190}, - {"month": "May", "desktop": 209, "mobile": 130}, - {"month": "Jun", "desktop": 214, "mobile": 140}, - ] - return rx.box( - info( - "Area Chart - Stacked", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Area Chart - Stacked"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.area_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.area( - data_key="desktop", - fill="var(--chart-1)", - stroke="var(--chart-1)", - stroke_width=2, - stack_id="a", - ), - rx.recharts.area( - data_key="mobile", - fill="var(--chart-2)", - stroke="var(--chart-2)", - stroke_width=2, - stack_id="a", + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.area( + data_key="mobile", + fill="var(--chart-2)", + stroke="var(--chart-2)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, ), - get_x_axis("month"), - data=data, - width="100%", - height=250, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` @@ -269,9 +376,6 @@ Demonstrates how data or series can update interactively in real-time. ```python def areachart_v5(): - import datetime - import random - from reflex.experimental import ClientStateVar start_date = datetime.date(2024, 4, 1) data = [ @@ -305,6 +409,8 @@ def areachart_v5(): stack_id="a", stroke=f"var(--{color})", animation_easing="linear", + is_animation_active=False, + active_dot={"fill": f"var(--{color})"}, ) select_options = [ @@ -313,53 +419,68 @@ def areachart_v5(): ("Last 7 Days", data[-7:]), ] - return rx.box( - rx.hstack( - info( - "Area Chart - Dynamic", - "3", - "Showing total visitors for the last 6 months", - "start", - ), - rx.el.select( - *[ - rx.el.option(label, on_click=SelectedRange.set_value(value)) - for label, value in select_options - ], - default_value="Last 3 Months", - bg=rx.color("gray", 2), - border=f"1px solid {rx.color('gray', 4)}", - class_name="relative flex items-center whitespace-nowrap justify-center gap-2 py-2 rounded-lg shadow-sm px-3", + return card.root( + card.header( + rx.el.div( + rx.el.div( + card.title("Area Chart - Dynamic"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + rx.el.select( + *[ + rx.el.option(label, on_click=SelectedRange.set_value(value)) + for label, value in select_options + ], + default_value="Last 3 Months", + class_name="relative flex items-center whitespace-nowrap justify-center gap-2 py-2 rounded-lg shadow-sm px-3 bg-secondary border border-input", + ), + class_name="flex flex-row flex-wrap gap-y-4 items-center justify-between", ), - align="center", - justify="between", - width="100%", - wrap="wrap", ), - rx.recharts.area_chart( - rx.el.svg.defs( - gradient("desktop", "chart-1"), - gradient("mobile", "chart-2"), + card.content( + rx.recharts.area_chart( + rx.el.svg.defs( + gradient("desktop", "chart-1"), + gradient("mobile", "chart-2"), + ), + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + area("mobile", "chart-2"), + area("desktop", "chart-1"), + rx.recharts.x_axis( + data_key="date", + axis_line=False, + min_tick_gap=32, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=SelectedRange.value, + width="100%", + height=240, ), - get_tooltip(), - get_cartesian_grid(), - area("mobile", "chart-2"), - area("desktop", "chart-1"), - rx.recharts.x_axis( - data_key="date", - axis_line=False, - min_tick_gap=32, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", + class_name="flex flex-col items-center h-[240px]", + ), + card.footer( + rx.el.div( + rx.foreach( + ["Desktop", "Mobile"], + lambda device, index: rx.el.div( + rx.el.div( + class_name=f"w-3 h-3 rounded-sm bg-chart-{index + 1}" + ), + rx.el.p(device, class_name="text-sm text-foreground"), + class_name="flex flex-row items-center gap-x-2", + ), + ), + class_name="py-4 px-4 flex w-full flex justify-center gap-8", ), - data=SelectedRange.value, - width="100%", - height=280, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` @@ -369,45 +490,65 @@ Adds a built-in legend for easy series identification. ```python def areachart_v6(): - data = [ - {"month": "Jan", "desktop": 186, "mobile": 80}, - {"month": "Feb", "desktop": 305, "mobile": 200}, - {"month": "Mar", "desktop": 237, "mobile": 120}, - {"month": "Apr", "desktop": 73, "mobile": 190}, - {"month": "May", "desktop": 209, "mobile": 130}, - {"month": "Jun", "desktop": 214, "mobile": 140}, - ] - return rx.box( - info( - "Area Chart - Legend", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Area Chart - Legend"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.area_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.area( - data_key="mobile", - fill="var(--chart-1)", - stroke="var(--chart-1)", - stack_id="a", - ), - rx.recharts.area( - data_key="desktop", - fill="var(--chart-2)", - stroke="var(--chart-2)", - stack_id="a", + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="mobile", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-2)", + stroke="var(--chart-2)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + rx.recharts.legend(), + data=data, + width="100%", + height=250, ), - get_x_axis("month"), - rx.recharts.legend(), - data=data, - width="100%", - height=250, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` @@ -417,59 +558,72 @@ Shows full control over axis configuration, labels, and styling. ```python def areachart_v7(): - data = [ - {"month": "Jan", "desktop": 186, "mobile": 80}, - {"month": "Feb", "desktop": 305, "mobile": 200}, - {"month": "Mar", "desktop": 237, "mobile": 120}, - {"month": "Apr", "desktop": 73, "mobile": 190}, - {"month": "May", "desktop": 209, "mobile": 130}, - {"month": "Jun", "desktop": 214, "mobile": 140}, - ] - return rx.box( - info( - "Area Chart - Axes", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Area Chart - Axes"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.area_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.area( - data_key="mobile", - fill="var(--chart-1)", - stroke="var(--chart-1)", - stack_id="a", - ), - rx.recharts.area( - data_key="desktop", - fill="var(--chart-2)", - stroke="var(--chart-2)", - stack_id="a", - ), - rx.recharts.x_axis( - data_key="month", - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", - ), - rx.recharts.y_axis( - width=30, - axis_line=False, - min_tick_gap=50, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="mobile", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-2)", + stroke="var(--chart-2)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + rx.recharts.y_axis( + width=30, + axis_line=False, + min_tick_gap=50, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + ), + data=data, + width="100%", + height=250, ), - data=data, - width="100%", - height=250, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` @@ -479,77 +633,102 @@ Implements a user-defined legend layout for better presentation control. ```python def areachart_v8(): - data = [ - {"month": "Jan", "desktop": 186, "mobile": 80}, - {"month": "Feb", "desktop": 305, "mobile": 200}, - {"month": "Mar", "desktop": 237, "mobile": 120}, - {"month": "Apr", "desktop": 73, "mobile": 190}, - {"month": "May", "desktop": 209, "mobile": 130}, - {"month": "Jun", "desktop": 214, "mobile": 140}, - ] - series = [("desktop", "Desktop", "--chart-1"), ("mobile", "Mobile", "--chart-2")] def create_gradient(var_name): - return rx.el.svg.defs( - rx.el.svg.linear_gradient( - rx.el.svg.stop( - stop_color=f"var({var_name})", offset="5%", stop_opacity=0.8 - ), - rx.el.svg.stop( - stop_color=f"var({var_name})", offset="95%", stop_opacity=0.1 - ), - x1=0, - x2=0, - y1=0, - y2=1, - id=var_name.strip("-"), - ) + return rx.el.svg.linear_gradient( + rx.el.svg.stop( + stop_color=f"var({var_name})", offset="5%", stop_opacity=0.8 + ), + rx.el.svg.stop( + stop_color=f"var({var_name})", offset="95%", stop_opacity=0.1 + ), + x1=0, + x2=0, + y1=0, + y2=1, + id=var_name.strip("-"), ) - return rx.box( - rx.hstack( - rx.foreach( - series, - lambda s: rx.hstack( - rx.box(class_name="w-3 h-3 rounded-sm", bg=f"var({s[2]})"), - rx.text( - s[1], - class_name="text-sm font-semibold", - color=rx.color("slate", 11), + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Area Chart - Gradient"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + rx.hstack( + rx.foreach( + series, + lambda s: rx.hstack( + rx.box(class_name="size-2 rounded-full", bg=f"var({s[2]})"), + rx.text( + s[1], + class_name="text-xs font-medium", + color=rx.color("slate", 11), + ), + align="center", + spacing="2", + ), ), - align="center", - spacing="2", + class_name="flex items-center gap-4", ), + align="center", + justify="between", + width="100%", ), - class_name="py-4 px-4 flex w-full justify-center gap-8", ), - rx.recharts.area_chart( - *(create_gradient(s[2]) for s in series), - get_tooltip(), - get_cartesian_grid(), - *( - rx.recharts.area( - data_key=s[0], - fill=f"url(#{s[2].strip('-')})", - stroke=f"var({s[2]})", - stack_id="1", - ) - for s in series - ), - rx.recharts.x_axis( - data_key="month", - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", + card.content( + rx.recharts.area_chart( + rx.el.svg.defs( + *(create_gradient(s[2]) for s in series), + ), + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + *( + rx.recharts.area( + data_key=s[0], + fill=f"url(#{s[2].strip('-')})", + stroke=f"var({s[2]})", + stroke_width=2, + stack_id="1", + is_animation_active=False, + ) + for s in series + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, ), - data=data, - width="100%", - height=250, ), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` @@ -559,14 +738,6 @@ Combines stepped transitions with a smooth color gradient for visual emphasis. ```python def areachart_v9(): - data = [ - {"month": "Jan", "desktop": 186}, - {"month": "Feb", "desktop": 305}, - {"month": "Mar", "desktop": 237}, - {"month": "Apr", "desktop": 73}, - {"month": "May", "desktop": 209}, - {"month": "Jun", "desktop": 214}, - ] def gradient(id_: str, color: str): return rx.el.svg.linear_gradient( @@ -581,39 +752,59 @@ def areachart_v9(): id=id_, ) - return rx.box( - info( - "Area Chart - Step with Gradient", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Area Chart - Step with Gradient"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.area_chart( - rx.el.svg.defs( - gradient("desktop", "chart-1"), - ), - get_tooltip(), - get_cartesian_grid(), - rx.recharts.area( - data_key="desktop", - fill="url(#desktop)", - stroke="var(--chart-1)", - stroke_width=2, - type_="step", - is_animation_active=False, + card.content( + rx.recharts.area_chart( + rx.el.svg.defs( + gradient("desktop", "chart-1"), + ), + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="desktop", + fill="url(#desktop)", + stroke="var(--chart-1)", + stroke_width=2, + type_="step", + is_animation_active=False, + active_dot={"fill": "var(--chart-1)"}, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, ), - get_x_axis("month"), - data=data, - width="100%", - height=250, ), - info( - "Trending up by 5.2% this month", - "2", - "January - June 2024", - "start", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -623,76 +814,97 @@ A complete example with custom legends, axes, and advanced styling combined. ```python def areachart_v10(): - data = [ - {"month": "Jan", "desktop": 186, "mobile": 80}, - {"month": "Feb", "desktop": 305, "mobile": 200}, - {"month": "Mar", "desktop": 237, "mobile": 120}, - {"month": "Apr", "desktop": 73, "mobile": 190}, - {"month": "May", "desktop": 209, "mobile": 130}, - {"month": "Jun", "desktop": 214, "mobile": 140}, - ] series = [("mobile", "Mobile", "--chart-1"), ("desktop", "Desktop", "--chart-2")] - return rx.box( - info( - "Area Chart - Stacked with Legend and Custom Axes", - "3", - "Showing total visitors for the last 6 months", - "start", - ), - rx.recharts.area_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.area( - data_key="mobile", - fill="var(--chart-1)", - stroke="var(--chart-1)", - stack_id="a", - ), - rx.recharts.area( - data_key="desktop", - fill="var(--chart-2)", - stroke="var(--chart-2)", - stack_id="a", - ), - rx.recharts.x_axis( - data_key="month", - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Area Chart - Mixed"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + rx.hstack( + rx.foreach( + series, + lambda s: rx.hstack( + rx.box(class_name="size-2 rounded-full", bg=f"var({s[2]})"), + rx.text( + s[1], + class_name="text-xs font-medium", + color=rx.color("slate", 11), + ), + align="center", + spacing="2", + ), + ), + class_name="flex items-center gap-4", + ), + align="center", + justify="between", + width="100%", ), - rx.recharts.y_axis( - width=30, - axis_line=False, - min_tick_gap=50, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, + ), + card.content( + rx.recharts.area_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.area( + data_key="mobile", + fill="var(--chart-1)", + stroke="var(--chart-1)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.area( + data_key="desktop", + fill="var(--chart-2)", + stroke="var(--chart-2)", + stroke_width=2, + stack_id="a", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + rx.recharts.y_axis( + width=30, + axis_line=False, + min_tick_gap=50, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + ), + data=data, + width="100%", + height=250, ), - data=data, - width="100%", - height=250, ), - rx.hstack( - rx.foreach( - series, - lambda s: rx.hstack( - rx.box(class_name="w-3 h-3 rounded-sm", bg=f"var({s[2]})"), - rx.text( - s[1], - class_name="text-sm font-semibold", - color=rx.color("slate", 11), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", ), - align="center", - spacing="2", + class_name="grid gap-2", ), - ), - class_name="py-2 px-4 flex w-full justify-center gap-8", + class_name="flex w-full items-start gap-2 text-sm", + ) ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` diff --git a/assets/docs/charts/bar-chart.md b/assets/docs/charts/bar-chart.md index 7c5be06f..e72aff03 100644 --- a/assets/docs/charts/bar-chart.md +++ b/assets/docs/charts/bar-chart.md @@ -6,75 +6,121 @@ Bar charts are ideal for visualizing categorical data and comparing multiple ser They can be stacked, oriented horizontally, or customized with legends and axes. # Usage -Copy the following helper functions into your Reflex application. +The chart tooltip components are available in the `base_ui` library. ```python -import reflex as rx - -tooltip = { - "is_animation_active": False, - "separator": "", - "cursor": False, - "item_style": { - "color": "currentColor", - "display": "flex", - "paddingBottom": "0px", - "justifyContent": "space-between", - "textTransform": "capitalize", - }, - "label_style": { - "color": rx.color("slate", 10), - "fontWeight": "500", - }, - "content_style": { - "background": rx.color_mode_cond("oklch(0.97 0.00 0)", "oklch(0.14 0.00 286)"), - "borderColor": rx.color("slate", 5), - "borderRadius": "5px", - "fontFamily": "IBM Plex Mono,ui-monospace,monospace", - "fontSize": "0.875rem", - "lineHeight": "1.25rem", - "fontWeight": "500", - "letterSpacing": "-0.01rem", - "minWidth": "8rem", - "width": "175px", - "padding": "0.375rem 0.625rem ", - "position": "relative", - }, -} - - -def info(title: str, size: str, subtitle: str, align: str): - return rx.vstack( - rx.heading(title, size=size, weight="bold"), - rx.text(subtitle, size="1", color=rx.color("slate", 11), weight="medium"), - spacing="1", - align=align, - ) - - -def get_tooltip(): - """Standard tooltip for all charts.""" - return rx.recharts.graphing_tooltip(**tooltip) +from typing import Literal +import reflex as rx -def get_cartesian_grid(): - """Standard cartesian grid for charts.""" - return rx.recharts.cartesian_grid( - horizontal=True, vertical=False, class_name="opacity-25" - ) +Display = Literal["show", "hide"] +Swatch = Literal["square", "line", "border"] + + +def _deep_merge(base: dict, override: dict) -> dict: + result = base.copy() + for key, value in override.items(): + if key in result and isinstance(result[key], dict) and isinstance(value, dict): + result[key] = _deep_merge(result[key], value) + else: + result[key] = value + return result + + +class _ChartTooltip: + def __call__( + self, + label: Display = "show", + is_animation_active: bool = False, + separator: str = "", + cursor: bool = False, + item_style: dict = {}, + label_style: dict = {}, + content_style: dict = {}, + ) -> rx.Component: + defaults = { + "is_animation_active": is_animation_active, + "separator": separator, + "cursor": cursor, + "item_style": _deep_merge( + { + "color": "currentColor", + "display": "flex", + "paddingBottom": "0px", + "justifyContent": "space-between", + "textTransform": "capitalize", + }, + item_style, + ), + "label_style": _deep_merge( + { + "display": "none" if label == "hide" else "flex", + "fontWeight": "500", + }, + label_style, + ), + "content_style": _deep_merge( + { + "background": "var(--background)", + "borderColor": "var(--input)", + "borderRadius": "0.85rem", + "padding": "0.25rem 0.65rem", + "position": "relative", + }, + content_style, + ), + } + return rx.recharts.graphing_tooltip(**defaults) + + +class _ChartTooltipContent: + def __call__(self, num_series: int, swatch: Swatch = "square") -> str: + base = """ + [&_.recharts-tooltip-item-name]:!text-muted-foreground + [&_.recharts-tooltip-item-separator]:!w-full + [&_.recharts-tooltip-item]:!w-[8rem] + [&_.recharts-tooltip-item]:!flex + [&_.recharts-tooltip-item]:!items-center + [&_.recharts-tooltip-item]:!gap-2 + """ + ( + """ + [&_.recharts-tooltip-label]:!border-l-3 + [&_.recharts-tooltip-label]:!border-[var(--chart-1)] + [&_.recharts-tooltip-label]:!pl-2 + [&_.recharts-tooltip-label]:!py-0 + """ + if swatch == "border" + else "" + ) -def get_x_axis(data_key: str): - """Standard X axis configuration.""" - return rx.recharts.x_axis( - data_key=data_key, - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", - ) + lines = [] + for i in range(1, num_series + 1): + if swatch == "border": + lines.append(f""" + [&_.recharts-default-tooltip]:!py-2 !flex !flex-col !gap-y-0 + [&_.recharts-tooltip-item:nth-child({i})]:!border-l-3 + [&_.recharts-tooltip-item:nth-child({i})]:!border-[var(--chart-{i})] + [&_.recharts-tooltip-item:nth-child({i})]:!pl-2 + [&_.recharts-tooltip-item:nth-child({i})]:!py-0 + """) + else: + lines.append(f""" + [&_.recharts-tooltip-item:nth-child({i})]:before:!content-[''] + [&_.recharts-tooltip-item:nth-child({i})]:before:{"!w-3" if swatch == "square" else "!w-8"} + {"[&_.recharts-tooltip-item:nth-child(" + str(i) + ")]:before:!flex-shrink-0" if swatch == "square" else ""} + [&_.recharts-tooltip-item:nth-child({i})]:before:!h-3 + [&_.recharts-tooltip-item:nth-child({i})]:before:!rounded-sm + [&_.recharts-tooltip-item:nth-child({i})]:before:!bg-[var(--chart-{i})] + [&_.recharts-tooltip-item:nth-child({i})]:before:!block + """) + + return base + "\n".join(lines) + + +chart_tooltip = _ChartTooltip() +chart_tooltip_content = _ChartTooltipContent() ``` @@ -86,42 +132,61 @@ A simple vertical bar chart comparing data categories. ```python def barchart_v1(): - data = [ - {"month": "Jan", "desktop": 186, "mobile": 80}, - {"month": "Feb", "desktop": 305, "mobile": 200}, - {"month": "Mar", "desktop": 237, "mobile": 120}, - {"month": "Apr", "desktop": 73, "mobile": 190}, - {"month": "May", "desktop": 209, "mobile": 130}, - {"month": "Jun", "desktop": 214, "mobile": 140}, - ] - return rx.box( - info( - "Bar Chart - Multiple", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Bar Chart - Multiple"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.bar_chart( - get_tooltip(), - get_cartesian_grid(), - rx.foreach( - ["desktop", "mobile"], - lambda name, index: rx.recharts.bar( - data_key=name, - fill=f"var(--chart-{index + 1})", - radius=6, + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" ), + rx.recharts.bar( + data_key="desktop", + fill="var(--chart-1)", + radius=4, + is_animation_active=False, + ), + rx.recharts.bar( + data_key="mobile", + fill="var(--chart-2)", + radius=4, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, ), - get_x_axis("month"), - data=data, - width="100%", - height=250, - bar_size=25, - bar_category_gap="30%", + class_name="h-[250px]", ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` @@ -131,47 +196,53 @@ Display multiple datasets within the same chart for comparison. ```python def barchart_v2(): - data = [ - {"month": "Jan", "desktop": 186}, - {"month": "Feb", "desktop": 305}, - {"month": "Mar", "desktop": 237}, - {"month": "Apr", "desktop": 73}, - {"month": "May", "desktop": 209}, - {"month": "Jun", "desktop": 214}, - ] - return rx.box( - info( - "Bar Chart - Horizontal", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Bar Chart - Horizontal"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.bar_chart( - get_tooltip(), - rx.recharts.bar( - data_key="desktop", - fill="var(--chart-1)", - radius=6, - ), - rx.recharts.x_axis(type_="number", hide=True, tick_size=0), - rx.recharts.y_axis( - data_key="month", - type_="category", - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.bar( + data_key="desktop", + fill="var(--chart-1)", + radius=4, + is_animation_active=False, + ), + rx.recharts.x_axis(type_="number", hide=True, tick_size=0), + rx.recharts.y_axis( + data_key="month", + type_="category", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + ), + data=data, + layout="vertical", + width="100%", + height=250, ), - data=data, - layout="vertical", - width="100%", - height=250, - bar_gap=2, - margin={"left": -20}, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -181,55 +252,64 @@ Combine related values by stacking bars for cumulative insights. ```python def barchart_v3(): - data = [ - {"month": "Jan", "desktop": 186, "mobile": 80}, - {"month": "Feb", "desktop": 305, "mobile": 200}, - {"month": "Mar", "desktop": 237, "mobile": 120}, - {"month": "Apr", "desktop": 73, "mobile": 190}, - {"month": "May", "desktop": 209, "mobile": 130}, - {"month": "Jun", "desktop": 214, "mobile": 140}, - ] - return rx.box( - info( - "Bar Chart - Legend", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Bar Chart - Legend"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.bar_chart( - get_cartesian_grid(), - get_tooltip(), - rx.recharts.bar( - data_key="desktop", - fill="var(--chart-1)", - stack_id="a", - radius=[0, 0, 6, 6], - ), - rx.recharts.bar( - data_key="mobile", - fill="var(--chart-2)", - stack_id="a", - radius=[6, 6, 0, 0], - ), - rx.recharts.y_axis(type_="number", hide=True), - rx.recharts.x_axis( - data_key="month", - type_="category", - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + data_key="desktop", + fill="var(--chart-1)", + stack_id="a", + radius=[0, 0, 4, 4], + is_animation_active=False, + ), + rx.recharts.bar( + data_key="mobile", + fill="var(--chart-2)", + stack_id="a", + radius=[4, 4, 0, 0], + is_animation_active=False, + ), + rx.recharts.y_axis(type_="number", hide=True), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + rx.recharts.legend(), + data=data, + width="100%", + height=250, ), - rx.recharts.legend(), - data=data, - width="100%", - height=250, - bar_gap=2, - bar_size=25, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` @@ -239,47 +319,61 @@ Flip the orientation to show bars horizontally for improved readability. ```python def barchart_v4(): - data = [ - {"month": "Jan", "desktop": 186}, - {"month": "Feb", "desktop": 340}, - {"month": "Mar", "desktop": 237}, - {"month": "Apr", "desktop": 73}, - {"month": "May", "desktop": 209}, - {"month": "Jun", "desktop": 214}, - ] - return rx.box( - info( - "Bar Chart - Labeled", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Bar Chart - Labeled"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.bar_chart( - get_cartesian_grid(), - get_tooltip(), - rx.recharts.bar( - rx.recharts.label_list( + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + rx.recharts.label_list( + data_key="desktop", + position="top", + offset=10, + ), data_key="desktop", - position="top", - stroke="10", - offset=10, - ), - data_key="desktop", - fill="var(--chart-1)", - stack_id="a", - radius=6, + fill="var(--chart-1)", + radius=4, + is_animation_active=False, + ), + rx.recharts.y_axis(type_="number", hide=True), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + margin={"top": 20}, ), - rx.recharts.y_axis(type_="number", hide=True), - get_x_axis("month"), - data=data, - width="100%", - height=250, - margin={"top": 25}, - bar_size=25, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -306,54 +400,6 @@ def barchart_v5(): {"date": "2024-04-12", "desktop": 292, "mobile": 210}, {"date": "2024-04-13", "desktop": 342, "mobile": 380}, {"date": "2024-04-14", "desktop": 137, "mobile": 220}, - {"date": "2024-04-15", "desktop": 120, "mobile": 170}, - {"date": "2024-04-16", "desktop": 138, "mobile": 190}, - {"date": "2024-04-17", "desktop": 446, "mobile": 360}, - {"date": "2024-04-18", "desktop": 364, "mobile": 410}, - {"date": "2024-04-19", "desktop": 243, "mobile": 180}, - {"date": "2024-04-20", "desktop": 89, "mobile": 150}, - {"date": "2024-04-21", "desktop": 137, "mobile": 200}, - {"date": "2024-04-22", "desktop": 224, "mobile": 170}, - {"date": "2024-04-23", "desktop": 138, "mobile": 230}, - {"date": "2024-04-24", "desktop": 387, "mobile": 290}, - {"date": "2024-04-25", "desktop": 215, "mobile": 250}, - {"date": "2024-04-26", "desktop": 75, "mobile": 130}, - {"date": "2024-04-27", "desktop": 383, "mobile": 420}, - {"date": "2024-04-28", "desktop": 122, "mobile": 180}, - {"date": "2024-04-29", "desktop": 315, "mobile": 240}, - {"date": "2024-04-30", "desktop": 454, "mobile": 380}, - {"date": "2024-05-01", "desktop": 165, "mobile": 220}, - {"date": "2024-05-02", "desktop": 293, "mobile": 310}, - {"date": "2024-05-03", "desktop": 247, "mobile": 190}, - {"date": "2024-05-04", "desktop": 385, "mobile": 420}, - {"date": "2024-05-05", "desktop": 481, "mobile": 390}, - {"date": "2024-05-06", "desktop": 498, "mobile": 520}, - {"date": "2024-05-07", "desktop": 388, "mobile": 300}, - {"date": "2024-05-08", "desktop": 149, "mobile": 210}, - {"date": "2024-05-09", "desktop": 227, "mobile": 180}, - {"date": "2024-05-10", "desktop": 293, "mobile": 330}, - {"date": "2024-05-11", "desktop": 335, "mobile": 270}, - {"date": "2024-05-12", "desktop": 197, "mobile": 240}, - {"date": "2024-05-13", "desktop": 197, "mobile": 160}, - {"date": "2024-05-14", "desktop": 448, "mobile": 490}, - {"date": "2024-05-15", "desktop": 473, "mobile": 380}, - {"date": "2024-05-16", "desktop": 338, "mobile": 400}, - {"date": "2024-05-17", "desktop": 499, "mobile": 420}, - {"date": "2024-05-18", "desktop": 315, "mobile": 350}, - {"date": "2024-05-19", "desktop": 235, "mobile": 180}, - {"date": "2024-05-20", "desktop": 177, "mobile": 230}, - {"date": "2024-05-21", "desktop": 82, "mobile": 140}, - {"date": "2024-05-22", "desktop": 81, "mobile": 120}, - {"date": "2024-05-23", "desktop": 252, "mobile": 290}, - {"date": "2024-05-24", "desktop": 294, "mobile": 220}, - {"date": "2024-05-25", "desktop": 201, "mobile": 250}, - {"date": "2024-05-26", "desktop": 213, "mobile": 170}, - {"date": "2024-05-27", "desktop": 420, "mobile": 460}, - {"date": "2024-05-28", "desktop": 233, "mobile": 190}, - {"date": "2024-05-29", "desktop": 78, "mobile": 130}, - {"date": "2024-05-30", "desktop": 340, "mobile": 280}, - {"date": "2024-05-31", "desktop": 178, "mobile": 230}, - {"date": "2024-06-01", "desktop": 178, "mobile": 200}, {"date": "2024-06-02", "desktop": 470, "mobile": 410}, {"date": "2024-06-03", "desktop": 103, "mobile": 160}, {"date": "2024-06-04", "desktop": 439, "mobile": 380}, @@ -381,43 +427,70 @@ def barchart_v5(): SelectedType = ClientStateVar.create("bar_selected", "mobile") - return rx.box( - rx.hstack( - info( - "Bar Chart - Dynamic", - "3", - "Showing total visitors for the last 6 months", - "start", - ), - rx.el.select( - rx.el.option("Mobile", on_click=SelectedType.set_value("mobile")), - rx.el.option("Desktop", on_click=SelectedType.set_value("desktop")), - default_value="Mobile", - bg=rx.color("gray", 2), - border=f"1px solid {rx.color('gray', 4)}", - class_name="relative flex items-center whitespace-nowrap justify-center gap-2 py-2 rounded-lg shadow-sm px-3", + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Bar Chart - Dynamic"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + rx.el.select( + rx.el.option("Mobile", on_click=SelectedType.set_value("mobile")), + rx.el.option("Desktop", on_click=SelectedType.set_value("desktop")), + default_value="Mobile", + bg=rx.color("gray", 2), + border=f"1px solid {rx.color('gray', 4)}", + class_name="relative flex items-center whitespace-nowrap justify-center gap-2 py-2 rounded-lg shadow-sm px-3", + ), + align="center", + justify="between", + width="100%", ), - align="center", - justify="between", - width="100%", - wrap="wrap", ), - rx.recharts.bar_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.bar( - data_key=SelectedType.value, - fill="var(--chart-1)", - radius=[2, 2, 0, 0], + card.content( + rx.recharts.bar_chart( + chart_tooltip("hide"), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + data_key=SelectedType.value, + fill="var(--chart-1)", + radius=[2, 2, 0, 0], + is_animation_active=False, + ), + rx.recharts.y_axis(type_="number", hide=True), + rx.recharts.x_axis( + data_key="date", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=formatted_data, + width="100%", + height=250, ), - rx.recharts.y_axis(type_="number", hide=True), - get_x_axis("date"), - data=formatted_data, - width="100%", - height=280, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -427,51 +500,58 @@ Add a built-in legend for clarity when displaying multiple series. ```python def barchart_v6(): - data = [ - {"month": "Jan", "desktop": 186}, - {"month": "Feb", "desktop": 340}, - {"month": "Mar", "desktop": 237, "active": True}, - {"month": "Apr", "desktop": 73}, - {"month": "May", "desktop": 209}, - {"month": "Jun", "desktop": 214}, - ] - - modified_data = [ - { - **item, - "stroke": ("var(--chart-3)" if item.get("active", False) else "none"), - } - for item in data - ] - return rx.box( - info( - "Bar Chart - Active", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Bar Chart - Active"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.bar_chart( - get_cartesian_grid(), - get_tooltip(), - rx.recharts.bar( - data_key="desktop", - fill="var(--chart-1)", - stack_id="a", - radius=6, - stroke="stroke", - stroke_width=3, + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + data_key="desktop", + fill="var(--chart-1)", + stack_id="a", + radius=4, + stroke="stroke", + stroke_width=2, + is_animation_active=False, + ), + rx.recharts.y_axis(type_="number", hide=True), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=modified_data, + width="100%", + height=250, ), - rx.recharts.y_axis(type_="number", hide=True), - get_x_axis("month"), - data=modified_data, - width="100%", - height=250, - margin={"top": 25}, - bar_size=25, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -481,45 +561,53 @@ Create a fully custom legend layout using Reflex components. ```python def barchart_v7(): - data = [ - {"browser": "Chrome", "visitors": 275, "fill": "var(--chart-1)"}, - {"browser": "Safari", "visitors": 200, "fill": "var(--chart-2)"}, - {"browser": "Firefox", "visitors": 187, "fill": "var(--chart-3)"}, - {"browser": "Edge", "visitors": 173, "fill": "var(--chart-4)"}, - {"browser": "Other", "visitors": 90, "fill": "var(--chart-5)"}, - ] - return rx.box( - info( - "Bar Chart - Mixed", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Bar Chart - Mixed"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.bar_chart( - get_tooltip(), - rx.recharts.bar( - data_key="visitors", - fill="fill", - radius=6, - ), - rx.recharts.x_axis(type_="number", hide=True, tick_size=0), - rx.recharts.y_axis( - data_key="browser", - type_="category", - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.bar( + data_key="visitors", + fill="fill", + radius=4, + is_animation_active=False, + ), + rx.recharts.x_axis(type_="number", hide=True, tick_size=0), + rx.recharts.y_axis( + data_key="browser", + type_="category", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + ), + data=data, + layout="vertical", + width="100%", + height=250, ), - data=data, - layout="vertical", - width="100%", - height=250, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - margin_right="20px", - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -529,97 +617,58 @@ Customize and format your x and y axes for improved presentation. ```python def barchart_v8(): - categories = ["Successful", "Refunded"] - EUROPE = [ - {"date": f"{month} 23", "Successful": successful, "Refunded": refunded} - for month, successful, refunded in zip( - [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ], - [12, 24, 48, 24, 34, 26, 12, 38, 23, 20, 24, 21], - [0, 1, 4, 2, 0, 0, 0, 2, 1, 0, 0, 8], - ) - ] - ASIA = [ - {"date": f"{month} 23", "Successful": successful, "Refunded": refunded} - for month, successful, refunded in zip( - [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ], - [31, 32, 44, 23, 35, 48, 33, 38, 41, 39, 32, 19], - [1, 2, 3, 2, 1, 1, 1, 3, 2, 1, 1, 5], - ) - ] - - def create_chart(data: list[dict[str, str | int]]): - return rx.recharts.bar_chart( - get_cartesian_grid(), - rx.foreach( - categories, - lambda key, index: rx.recharts.bar( - data_key=key, - fill=f"var(--chart-{index + 1})", - stack_id="_", + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Online Transactions"), + card.description("Global revenue distributions"), + class_name="flex flex-col gap-y-1.5", + ), + rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger( + rx.text("Europe", class_name="text-xs font-semibold"), + value="1", + ), + rx.tabs.trigger( + rx.text("Asia", class_name="text-xs font-semibold"), + value="2", + ), + ), + default_value="1", + id="transactions-tabs", ), + align="center", + justify="between", + width="100%", ), - rx.recharts.x_axis( - interval=10, - data_key="date", - tick_size=10, - class_name="text-xs font-semibold", - axis_line=False, - tick_line=False, + ), + card.content( + rx.tabs.root( + rx.tabs.content(create_chart(EUROPE), value="1"), + rx.tabs.content(create_chart(ASIA), value="2"), + default_value="1", ), - get_tooltip(), - data=data, - width="100%", - height=250, - bar_size=25, - ) - - return rx.box( - rx.text("Online Transactions", class_name="text-md font-semibold pb-3"), - rx.tabs.root( - rx.tabs.list( - rx.tabs.trigger( - rx.text("Europe", class_name="text-sm font-semibold"), - flex="1", - value="1", - ), - rx.tabs.trigger( - rx.text("Asia", class_name="text-sm font-semibold"), - flex="1", - value="2", + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", ), - ), - rx.tabs.content(create_chart(EUROPE), value="1", margin_top="-5px"), - rx.tabs.content(create_chart(ASIA), value="2", margin_top="-5px"), - default_value="1", + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` @@ -629,46 +678,83 @@ Show how the chart adapts across different screen sizes and layouts. ```python def barchart_v9(): - data = [ - {"month": "Jan", "desktop": 186, "mobile": 80, "tablet": 50}, - {"month": "Feb", "desktop": 305, "mobile": 200, "tablet": 120}, - {"month": "Mar", "desktop": 237, "mobile": 120, "tablet": 70}, - {"month": "Apr", "desktop": 73, "mobile": 190, "tablet": 30}, - {"month": "May", "desktop": 209, "mobile": 130, "tablet": 80}, - ] - return rx.box( - rx.hstack( - rx.foreach( - [ - ["Desktop", "var(--chart-1)"], - ["Mobile", "var(--chart-2)"], - ["Tablet", "var(--chart-3)"], - ], - lambda key: rx.hstack( - rx.box(class_name="w-3 h-3 rounded-sm", bg=key[1]), - rx.text( - key[0], - class_name="text-sm font-semibold", - color=rx.color("slate", 11), + return card.root( + card.header( + rx.el.div( + card.title("Bar Chart - Multiple"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", + ), + ), + card.content( + rx.recharts.bar_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.bar( + data_key="desktop", + fill="var(--chart-1)", + radius=4, + is_animation_active=False, + ), + rx.recharts.bar( + data_key="mobile", + fill="var(--chart-2)", + radius=4, + is_animation_active=False, + ), + rx.recharts.bar( + data_key="tablet", + fill="var(--chart-3)", + radius=4, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=230, + ), + rx.el.div( + rx.foreach( + ["Desktop", "Mobile", "Tablet"], + lambda device, index: rx.el.div( + rx.el.div( + class_name=f"h-3 w-3 rounded-sm bg-chart-{index + 1}" + ), + rx.el.p(device, class_name="text-xs text-foreground"), + class_name="flex flex-row items-center gap-x-2", ), - align="center", - spacing="2", ), + class_name="flex items-center gap-4 justify-center", ), - class_name="py-4 px-4 flex w-full flex justify-center gap-8", + class_name="flex flex-col h-[250px]", ), - rx.recharts.bar_chart( - get_tooltip(), - rx.recharts.bar(data_key="desktop", fill="var(--chart-1)", radius=4), - rx.recharts.bar(data_key="mobile", fill="var(--chart-2)", radius=4), - rx.recharts.bar(data_key="tablet", fill="var(--chart-3)", radius=4), - get_x_axis("month"), - data=data, - width="100%", - height=250, + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + class_name=chart_tooltip_content(3, "square") + " w-full p-0", ) ``` @@ -678,68 +764,62 @@ Apply gradient fills to your bars for a modern, polished look. ```python def barchart_v10(): - sport = [ - {"date": "Jan 23", "Running": 167, "Cycling": 145}, - {"date": "Feb 23", "Running": 125, "Cycling": 110}, - {"date": "Mar 23", "Running": 156, "Cycling": 149}, - {"date": "Apr 23", "Running": 165, "Cycling": 112}, - {"date": "May 23", "Running": 153, "Cycling": 138}, - {"date": "Jun 23", "Running": 124, "Cycling": 145}, - {"date": "Jul 23", "Running": 164, "Cycling": 134}, - ] - - activities = ["Running", "Cycling"] - chart_colors = ["var(--chart-1)", "var(--chart-2)"] - - def create_alternating_chart(active_key: str): - return rx.recharts.bar_chart( - get_tooltip(), - *[ - rx.recharts.bar( - is_animation_active=False, - radius=4, - data_key=key, - fill=color, - custom_attrs={ - "opacity": rx.cond( - key == active_key, - "0.25", - "1", - ) - }, - ) - for key, color in zip(activities, chart_colors) - ], - get_x_axis("date"), - data=sport, - width="100%", - height=250, - bar_category_gap="20%", - ) - return rx.box( - rx.tabs.root( - rx.tabs.list( + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Sport Activities"), + card.description("Running vs Cycling load"), + class_name="flex flex-col gap-y-1.5", + ), + rx.tabs.root( + rx.tabs.list( + *[ + rx.tabs.trigger( + rx.text(activity, class_name="text-xs font-semibold"), + value=str(i + 1), + ) + for i, activity in enumerate(activities) + ] + ), + default_value="1", + ), + align="center", + justify="between", + width="100%", + ), + ), + card.content( + rx.tabs.root( *[ - rx.tabs.trigger( - rx.text(activity, class_name="text-sm font-semibold"), + rx.tabs.content( + create_alternating_chart(active), value=str(i + 1), ) - for i, activity in enumerate(activities) - ] + for i, active in enumerate(activities) + ], + default_value="1", + width="100%", ), - *[ - rx.tabs.content( - create_alternating_chart(active), - value=str(i + 1), - margin_top="-5px", - ) - for i, active in enumerate(activities) - ], - default_value="1", - width="100%", ), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` diff --git a/assets/docs/charts/doughnut-chart.md b/assets/docs/charts/doughnut-chart.md index 7793e4da..16c19e1e 100644 --- a/assets/docs/charts/doughnut-chart.md +++ b/assets/docs/charts/doughnut-chart.md @@ -4,79 +4,6 @@ Doughnut Charts are ideal for showing changes over time or the magnitude of multiple datasets stacked together. They combine the smoothness of line charts with the visual impact of filled areas. -# Usage -Copy the following helper functions into your Reflex application. - - -```python -import reflex as rx - -tooltip = { - "is_animation_active": False, - "separator": "", - "cursor": False, - "item_style": { - "color": "currentColor", - "display": "flex", - "paddingBottom": "0px", - "justifyContent": "space-between", - "textTransform": "capitalize", - }, - "label_style": { - "color": rx.color("slate", 10), - "fontWeight": "500", - }, - "content_style": { - "background": rx.color_mode_cond("oklch(0.97 0.00 0)", "oklch(0.14 0.00 286)"), - "borderColor": rx.color("slate", 5), - "borderRadius": "5px", - "fontFamily": "IBM Plex Mono,ui-monospace,monospace", - "fontSize": "0.875rem", - "lineHeight": "1.25rem", - "fontWeight": "500", - "letterSpacing": "-0.01rem", - "minWidth": "8rem", - "width": "175px", - "padding": "0.375rem 0.625rem ", - "position": "relative", - }, -} - - -def info(title: str, size: str, subtitle: str, align: str): - return rx.vstack( - rx.heading(title, size=size, weight="bold"), - rx.text(subtitle, size="1", color=rx.color("slate", 11), weight="medium"), - spacing="1", - align=align, - ) - - -def get_tooltip(): - """Standard tooltip for all charts.""" - return rx.recharts.graphing_tooltip(**tooltip) - - -def get_cartesian_grid(): - """Standard cartesian grid for charts.""" - return rx.recharts.cartesian_grid( - horizontal=True, vertical=False, class_name="opacity-25" - ) - - -def get_x_axis(data_key: str): - """Standard X axis configuration.""" - return rx.recharts.x_axis( - data_key=data_key, - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", - ) -``` - - # Examples Below are examples demonstrating how these components and charts can be used. @@ -85,57 +12,68 @@ A customizable doughnut chart with flexible styling and data visualization optio ```python def doughnutchart_v1(): - data = [ - {"browser": "chrome", "visitors": 275}, - {"browser": "safari", "visitors": 200}, - {"browser": "firefox", "visitors": 187}, - {"browser": "edge", "visitors": 173}, - {"browser": "other", "visitors": 90}, - ] - return rx.el.div( - rx.recharts.pie_chart( - rx.recharts.pie( + return card.root( + card.header( + card.title("Doughnut Chart"), + card.description("Browser distribution - Last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + [1, 2, 3, 4, 5], + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), + ), + data=data, + data_key="visitors", + name_key="browser", + inner_radius=60, + stroke_width=5, + stroke="var(--background)", + is_animation_active=False, + custom_attrs={"paddingAngle": 3, "cornerRadius": 5}, + ), + width="100%", + height=220, + ), + rx.el.div( rx.foreach( - ["red", "blue", "green", "amber", "purple"], - lambda color, index: rx.recharts.cell( - fill="var(--chart-2)", class_name=f"theme-{color}" + ["chrome", "safari", "firefox", "edge", "other"], + lambda browser, index: rx.el.div( + rx.el.div( + class_name=f"w-3 h-3 rounded-sm bg-chart-{index + 1}" + ), + rx.el.p( + browser, + class_name="text-sm font-semibold text-foreground capitalize", + ), + class_name="flex flex-row gap-x-2 items-center", ), ), - data=data, - data_key="visitors", - name_key="browser", - stroke="0", - inner_radius=90, - custom_attrs={"paddingAngle": 3, "cornerRadius": 5}, + class_name="w-full flex flex-row flex-wrap gap-2 items-center justify-center", ), - width="100%", - height=350, - class_name="w-[100%] [&_.recharts-tooltip-item-separator]:w-full", + class_name="flex flex-col h-[250px] items-center", ), - rx.hstack( - rx.foreach( - [ - ["Chrome", "red"], - ["Safari", "blue"], - ["Firefox", "green"], - ["Edge", "amber"], - ["Other", "purple"], - ], - lambda key: rx.hstack( - rx.box(class_name="w-3 h-3 rounded-sm", bg=rx.color(key[1])), - rx.text( - key[0], - class_name="text-sm font-semibold", - color=rx.color("slate", 11), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", ), - align="center", - spacing="2", + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", ), - ), - class_name="py-4 px-4 flex w-full justify-center flex-wrap", + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="flex flex-col size-full relative", + class_name="w-full p-0", ) ``` @@ -145,43 +83,62 @@ Displays a doughnut chart with centered label text for enhanced data presentatio ```python def doughnutchart_v2(): - data = [ - {"browser": "chrome", "visitors": 275}, - {"browser": "safari", "visitors": 200}, - {"browser": "firefox", "visitors": 187}, - {"browser": "edge", "visitors": 173}, - {"browser": "other", "visitors": 90}, - ] total_visitors = sum(item["visitors"] for item in data) - return rx.el.div( - rx.el.div( - rx.el.label(total_visitors, class_name="text-4xl font-bold"), - rx.el.label("Total Visitors", class_name="text-sm font-regular"), - class_name="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex flex-col justify-center align-center items-center", + return card.root( + card.header( + card.title("Doughnut Chart - Stacked"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.pie_chart( - rx.recharts.pie( - rx.foreach( - ["red", "blue", "green", "amber", "purple"], - lambda color, index: rx.recharts.cell( - fill="var(--chart-2)", class_name=f"theme-{color}" + card.content( + rx.el.div( + rx.el.div( + rx.el.p( + str(total_visitors), + class_name="text-3xl font-bold text-foreground", + ), + rx.el.p( + "Visitors", + class_name="text-xs text-muted-foreground", + ), + class_name="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex flex-col items-center justify-center", + ), + rx.recharts.pie_chart( + # + rx.recharts.pie( + data=data, + data_key="visitors", + name_key="browser", + inner_radius=60, + stroke_width=5, + stroke="var(--background)", + is_animation_active=False, ), + width="100%", + height=250, ), - data=data, - data_key="visitors", - name_key="browser", - stroke="0", - inner_radius=90, - custom_attrs={"paddingAngle": 3, "cornerRadius": 5}, - class_name="recharts-sector darK:hover:brightness-125 transition duration-200 ease", + class_name="relative w-full", ), - width="100%", - height=350, - class_name="w-[100%] [&_.recharts-tooltip-item-separator]:w-full", ), - class_name="flex flex-col size-full relative", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + # , + class_name="w-full p-0", ) ``` diff --git a/assets/docs/charts/line-chart.md b/assets/docs/charts/line-chart.md index 99c6f3fd..aa7b3e75 100644 --- a/assets/docs/charts/line-chart.md +++ b/assets/docs/charts/line-chart.md @@ -5,75 +5,121 @@ Line Charts are ideal for showing changes over time or the magnitude of multiple datasets stacked together. They combine the smoothness of line charts with the visual impact of filled areas. # Usage -Copy the following helper functions into your Reflex application. +The chart tooltip components are available in the `base_ui` library. ```python -import reflex as rx - -tooltip = { - "is_animation_active": False, - "separator": "", - "cursor": False, - "item_style": { - "color": "currentColor", - "display": "flex", - "paddingBottom": "0px", - "justifyContent": "space-between", - "textTransform": "capitalize", - }, - "label_style": { - "color": rx.color("slate", 10), - "fontWeight": "500", - }, - "content_style": { - "background": rx.color_mode_cond("oklch(0.97 0.00 0)", "oklch(0.14 0.00 286)"), - "borderColor": rx.color("slate", 5), - "borderRadius": "5px", - "fontFamily": "IBM Plex Mono,ui-monospace,monospace", - "fontSize": "0.875rem", - "lineHeight": "1.25rem", - "fontWeight": "500", - "letterSpacing": "-0.01rem", - "minWidth": "8rem", - "width": "175px", - "padding": "0.375rem 0.625rem ", - "position": "relative", - }, -} - - -def info(title: str, size: str, subtitle: str, align: str): - return rx.vstack( - rx.heading(title, size=size, weight="bold"), - rx.text(subtitle, size="1", color=rx.color("slate", 11), weight="medium"), - spacing="1", - align=align, - ) - +from typing import Literal -def get_tooltip(): - """Standard tooltip for all charts.""" - return rx.recharts.graphing_tooltip(**tooltip) - - -def get_cartesian_grid(): - """Standard cartesian grid for charts.""" - return rx.recharts.cartesian_grid( - horizontal=True, vertical=False, class_name="opacity-25" - ) +import reflex as rx +Display = Literal["show", "hide"] +Swatch = Literal["square", "line", "border"] + + +def _deep_merge(base: dict, override: dict) -> dict: + result = base.copy() + for key, value in override.items(): + if key in result and isinstance(result[key], dict) and isinstance(value, dict): + result[key] = _deep_merge(result[key], value) + else: + result[key] = value + return result + + +class _ChartTooltip: + def __call__( + self, + label: Display = "show", + is_animation_active: bool = False, + separator: str = "", + cursor: bool = False, + item_style: dict = {}, + label_style: dict = {}, + content_style: dict = {}, + ) -> rx.Component: + defaults = { + "is_animation_active": is_animation_active, + "separator": separator, + "cursor": cursor, + "item_style": _deep_merge( + { + "color": "currentColor", + "display": "flex", + "paddingBottom": "0px", + "justifyContent": "space-between", + "textTransform": "capitalize", + }, + item_style, + ), + "label_style": _deep_merge( + { + "display": "none" if label == "hide" else "flex", + "fontWeight": "500", + }, + label_style, + ), + "content_style": _deep_merge( + { + "background": "var(--background)", + "borderColor": "var(--input)", + "borderRadius": "0.85rem", + "padding": "0.25rem 0.65rem", + "position": "relative", + }, + content_style, + ), + } -def get_x_axis(data_key: str): - """Standard X axis configuration.""" - return rx.recharts.x_axis( - data_key=data_key, - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", - ) + return rx.recharts.graphing_tooltip(**defaults) + + +class _ChartTooltipContent: + def __call__(self, num_series: int, swatch: Swatch = "square") -> str: + base = """ + [&_.recharts-tooltip-item-name]:!text-muted-foreground + [&_.recharts-tooltip-item-separator]:!w-full + [&_.recharts-tooltip-item]:!w-[8rem] + [&_.recharts-tooltip-item]:!flex + [&_.recharts-tooltip-item]:!items-center + [&_.recharts-tooltip-item]:!gap-2 + """ + ( + """ + [&_.recharts-tooltip-label]:!border-l-3 + [&_.recharts-tooltip-label]:!border-[var(--chart-1)] + [&_.recharts-tooltip-label]:!pl-2 + [&_.recharts-tooltip-label]:!py-0 + """ + if swatch == "border" + else "" + ) + + lines = [] + for i in range(1, num_series + 1): + if swatch == "border": + lines.append(f""" + [&_.recharts-default-tooltip]:!py-2 !flex !flex-col !gap-y-0 + [&_.recharts-tooltip-item:nth-child({i})]:!border-l-3 + [&_.recharts-tooltip-item:nth-child({i})]:!border-[var(--chart-{i})] + [&_.recharts-tooltip-item:nth-child({i})]:!pl-2 + [&_.recharts-tooltip-item:nth-child({i})]:!py-0 + """) + else: + lines.append(f""" + [&_.recharts-tooltip-item:nth-child({i})]:before:!content-[''] + [&_.recharts-tooltip-item:nth-child({i})]:before:{"!w-3" if swatch == "square" else "!w-8"} + {"[&_.recharts-tooltip-item:nth-child(" + str(i) + ")]:before:!flex-shrink-0" if swatch == "square" else ""} + [&_.recharts-tooltip-item:nth-child({i})]:before:!h-3 + [&_.recharts-tooltip-item:nth-child({i})]:before:!rounded-sm + [&_.recharts-tooltip-item:nth-child({i})]:before:!bg-[var(--chart-{i})] + [&_.recharts-tooltip-item:nth-child({i})]:before:!block + """) + + return base + "\n".join(lines) + + +chart_tooltip = _ChartTooltip() +chart_tooltip_content = _ChartTooltipContent() ``` @@ -85,39 +131,56 @@ A minimal example showing a single series with a smooth line connection. ```python def linechart_v1(): - data = [ - {"month": "Jan", "desktop": 186}, - {"month": "Feb", "desktop": 305}, - {"month": "Mar", "desktop": 237}, - {"month": "Apr", "desktop": 73}, - {"month": "May", "desktop": 209}, - {"month": "Jun", "desktop": 214}, - ] - return rx.box( - info( - "Line Chart", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Line Chart"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.line_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.line( - data_key="desktop", - stroke="var(--chart-1)", - stroke_width=2, - type_="natural", - dot=False, + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key="desktop", + stroke="var(--chart-1)", + stroke_width=2, + type_="natural", + dot=False, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, ), - get_x_axis("month"), - data=data, - width="100%", - height=250, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -127,39 +190,56 @@ Displays data using straight line segments between points. ```python def linechart_v2(): - data = [ - {"month": "Jan", "desktop": 186}, - {"month": "Feb", "desktop": 305}, - {"month": "Mar", "desktop": 237}, - {"month": "Apr", "desktop": 73}, - {"month": "May", "desktop": 209}, - {"month": "Jun", "desktop": 214}, - ] - return rx.box( - info( - "Line Chart - Linear", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Line Chart - Linear"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.line_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.line( - data_key="desktop", - stroke="var(--chart-1)", - stroke_width=2, - type_="linear", - dot=False, + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key="desktop", + stroke="var(--chart-1)", + stroke_width=2, + type_="linear", + dot=False, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, ), - get_x_axis("month"), - data=data, - width="100%", - height=250, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -169,45 +249,62 @@ Shows data points with labels for clear value identification. ```python def linechart_v3(): - data = [ - {"month": "Jan", "desktop": 186}, - {"month": "Feb", "desktop": 305}, - {"month": "Mar", "desktop": 237}, - {"month": "Apr", "desktop": 73}, - {"month": "May", "desktop": 209}, - {"month": "Jun", "desktop": 214}, - ] - return rx.box( - info( - "Line Chart - Label", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Line Chart - Label"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.line_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.line( - rx.recharts.label_list( - position="top", - offset=20, - custom_attrs={"fontSize": "12px", "fontWeight": "bold"}, - ), - data_key="desktop", - stroke="var(--chart-1)", - stroke_width=2, - type_="linear", - dot=True, + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + rx.recharts.label_list( + position="top", + offset=20, + custom_attrs={"fontSize": "12px", "fontWeight": "bold"}, + ), + data_key="desktop", + stroke="var(--chart-1)", + stroke_width=2, + type_="linear", + dot=True, + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=250, + margin={"left": 20, "right": 20, "top": 25}, ), - get_x_axis("month"), - data=data, - width="100%", - height=250, - margin={"left": 20, "right": 20, "top": 25}, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -217,43 +314,64 @@ Visualizes multiple data series on the same chart for comparison. ```python def linechart_v4(): - data = [ - {"month": "Jan", "desktop": 186, "mobile": 80}, - {"month": "Feb", "desktop": 305, "mobile": 200}, - {"month": "Mar", "desktop": 237, "mobile": 120}, - {"month": "Apr", "desktop": 73, "mobile": 190}, - {"month": "May", "desktop": 209, "mobile": 130}, - {"month": "Jun", "desktop": 214, "mobile": 140}, - ] - return rx.box( - info( - "Line Chart - Multiple", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Line Chart - Multiple"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.line_chart( - get_tooltip(), - get_cartesian_grid(), - rx.foreach( - ["desktop", "mobile"], - lambda name, index: rx.recharts.line( - data_key=name, - stroke=f"var(--chart-{index + 1})", + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key="desktop", + stroke="var(--chart-1)", + stroke_width=2, + type_="natural", + dot=False, + is_animation_active=False, + ), + rx.recharts.line( + data_key="mobile", + stroke="var(--chart-2)", stroke_width=2, type_="natural", dot=False, - stack_id="a", + is_animation_active=False, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", ), + data=data, + width="100%", + height=250, ), - get_x_axis("month"), - data=data, - width="100%", - height=250, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` @@ -263,44 +381,56 @@ Includes a title and labels for comprehensive chart context. ```python def linechart_v5(): - data = [ - {"browser": "chrome", "visitors": 275}, - {"browser": "safari", "visitors": 200}, - {"browser": "firefox", "visitors": 187}, - {"browser": "edge", "visitors": 173}, - {"browser": "other", "visitors": 90}, - ] - return rx.box( - info( - "Line Chart - Title Label", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Line Chart - Title Label"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.line_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.line( - rx.recharts.label_list( - position="top", - offset=20, - custom_attrs={"fontSize": "12px", "fontWeight": "bold"}, - data_key="browser", - ), - data_key="visitors", - stroke="var(--chart-1)", - stroke_width=2, - type_="natural", - dot=True, + card.content( + rx.recharts.line_chart( + chart_tooltip("hide"), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + rx.recharts.label_list( + position="top", + offset=20, + custom_attrs={"fontSize": "12px", "fontWeight": "bold"}, + data_key="browser", + ), + data_key="visitors", + stroke="var(--chart-1)", + stroke_width=2, + type_="natural", + dot=True, + is_animation_active=False, + active_dot={"fill": "var(--chart-1)"}, + ), + data=data, + width="100%", + height=250, + margin={"left": 25, "right": 20, "top": 25}, ), - data=data, - width="100%", - height=250, - margin={"left": 25, "right": 20, "top": 25}, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -310,38 +440,49 @@ A clean, stripped-down version focusing on essential data visualization. ```python def linechart_v6(): - data = [ - {"browser": "chrome", "visitors": 275}, - {"browser": "safari", "visitors": 200}, - {"browser": "firefox", "visitors": 187}, - {"browser": "edge", "visitors": 173}, - {"browser": "other", "visitors": 90}, - ] - return rx.box( - info( - "Line Chart - Minimal", - "3", - "Showing total visitors for the last 6 months", - "start", + return card.root( + card.header( + card.title("Line Chart - Minimal"), + card.description("Showing total visitors for the last 6 months"), ), - rx.recharts.line_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.line( - data_key="visitors", - type_="natural", - dot=False, - stroke="var(--chart-1)", - stroke_width=2, + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key="visitors", + type_="natural", + dot=False, + stroke="var(--chart-1)", + stroke_width=2, + is_animation_active=False, + ), + data=data, + width="100%", + height=250, + margin={"left": 20, "right": 20, "top": 25}, ), - data=data, - width="100%", - height=250, - margin={"left": 20, "right": 20, "top": 25}, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "square") + " w-full p-0", ) ``` @@ -368,52 +509,6 @@ def linechart_v7(): {"date": "2024-04-12", "desktop": 292, "mobile": 210}, {"date": "2024-04-13", "desktop": 342, "mobile": 380}, {"date": "2024-04-14", "desktop": 137, "mobile": 220}, - {"date": "2024-04-15", "desktop": 120, "mobile": 170}, - {"date": "2024-04-16", "desktop": 138, "mobile": 190}, - {"date": "2024-04-17", "desktop": 446, "mobile": 360}, - {"date": "2024-04-18", "desktop": 364, "mobile": 410}, - {"date": "2024-04-19", "desktop": 243, "mobile": 180}, - {"date": "2024-04-20", "desktop": 89, "mobile": 150}, - {"date": "2024-04-21", "desktop": 137, "mobile": 200}, - {"date": "2024-04-22", "desktop": 224, "mobile": 170}, - {"date": "2024-04-23", "desktop": 138, "mobile": 230}, - {"date": "2024-04-24", "desktop": 387, "mobile": 290}, - {"date": "2024-04-25", "desktop": 215, "mobile": 250}, - {"date": "2024-04-26", "desktop": 75, "mobile": 130}, - {"date": "2024-04-27", "desktop": 383, "mobile": 420}, - {"date": "2024-04-28", "desktop": 122, "mobile": 180}, - {"date": "2024-04-29", "desktop": 315, "mobile": 240}, - {"date": "2024-04-30", "desktop": 454, "mobile": 380}, - {"date": "2024-05-01", "desktop": 165, "mobile": 220}, - {"date": "2024-05-02", "desktop": 293, "mobile": 310}, - {"date": "2024-05-03", "desktop": 247, "mobile": 190}, - {"date": "2024-05-04", "desktop": 385, "mobile": 420}, - {"date": "2024-05-05", "desktop": 481, "mobile": 390}, - {"date": "2024-05-06", "desktop": 498, "mobile": 520}, - {"date": "2024-05-07", "desktop": 388, "mobile": 300}, - {"date": "2024-05-08", "desktop": 149, "mobile": 210}, - {"date": "2024-05-09", "desktop": 227, "mobile": 180}, - {"date": "2024-05-10", "desktop": 293, "mobile": 330}, - {"date": "2024-05-11", "desktop": 335, "mobile": 270}, - {"date": "2024-05-12", "desktop": 197, "mobile": 240}, - {"date": "2024-05-13", "desktop": 197, "mobile": 160}, - {"date": "2024-05-14", "desktop": 448, "mobile": 490}, - {"date": "2024-05-15", "desktop": 473, "mobile": 380}, - {"date": "2024-05-16", "desktop": 338, "mobile": 400}, - {"date": "2024-05-17", "desktop": 499, "mobile": 420}, - {"date": "2024-05-18", "desktop": 315, "mobile": 350}, - {"date": "2024-05-19", "desktop": 235, "mobile": 180}, - {"date": "2024-05-20", "desktop": 177, "mobile": 230}, - {"date": "2024-05-21", "desktop": 82, "mobile": 140}, - {"date": "2024-05-22", "desktop": 81, "mobile": 120}, - {"date": "2024-05-23", "desktop": 252, "mobile": 290}, - {"date": "2024-05-24", "desktop": 294, "mobile": 220}, - {"date": "2024-05-25", "desktop": 201, "mobile": 250}, - {"date": "2024-05-26", "desktop": 213, "mobile": 170}, - {"date": "2024-05-27", "desktop": 420, "mobile": 460}, - {"date": "2024-05-28", "desktop": 233, "mobile": 190}, - {"date": "2024-05-29", "desktop": 78, "mobile": 130}, - {"date": "2024-05-30", "desktop": 340, "mobile": 280}, {"date": "2024-05-31", "desktop": 178, "mobile": 230}, {"date": "2024-06-01", "desktop": 178, "mobile": 200}, {"date": "2024-06-02", "desktop": 470, "mobile": 410}, @@ -443,53 +538,78 @@ def linechart_v7(): ] SelectedType = ClientStateVar.create("selected_line", "mobile") - DotTrigger = ClientStateVar.create("dot_trigger", False) - - return rx.box( - rx.hstack( - info( - "Line Chart - Dynamic", - "3", - "Showing total visitors for the last 6 months", - "start", - ), - rx.hstack( - rx.checkbox( - "Show Dots", on_change=DotTrigger.set_value(~DotTrigger.value) + + return card.root( + card.header( + rx.el.div( + rx.el.div( + card.title("Line Chart - Dynamic"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", ), - rx.el.select( - rx.el.option("Mobile", on_click=SelectedType.set_value("mobile")), - rx.el.option("Desktop", on_click=SelectedType.set_value("desktop")), - default_value="Mobile", - bg=rx.color("gray", 2), - border=f"1px solid {rx.color('gray', 4)}", - class_name="relative flex items-center whitespace-nowrap justify-center gap-2 py-2 rounded-lg shadow-sm px-3", + rx.el.div( + rx.el.select( + rx.el.option( + "Mobile", on_click=SelectedType.set_value("mobile") + ), + rx.el.option( + "Desktop", on_click=SelectedType.set_value("desktop") + ), + default_value="Mobile", + class_name="relative flex items-center whitespace-nowrap justify-center gap-2 py-2 rounded-lg shadow-sm px-3 bg-secondary border border-input", + ), + class_name="flex flex-row items-center gap-x-2", ), - align="center", + class_name="w-full flex flex-row flex-wrap items-center justify-between gap-y-4", ), - align="center", - justify="between", - width="100%", - wrap="wrap", ), - rx.recharts.line_chart( - get_tooltip(), - get_cartesian_grid(), - rx.recharts.line( - data_key=SelectedType.value, - stroke="var(--chart-1)", - stroke_width=2, - type_="natural", - dot=DotTrigger.value, + card.content( + rx.recharts.line_chart( + chart_tooltip("hide"), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key=SelectedType.value, + stroke="var(--chart-1)", + stroke_width=2, + type_="natural", + is_animation_active=False, + dot=False, + active_dot={"fill": "var(--chart-1)"}, + ), + rx.recharts.y_axis(type_="number", hide=True), + rx.recharts.x_axis( + data_key="date", + axis_line=False, + min_tick_gap=32, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=formatted_data, + width="100%", + height=250, ), - rx.recharts.y_axis(type_="number", hide=True), - get_x_axis("date"), - data=formatted_data, - width="100%", - height=280, ), - info("Trending up by 5.2% this month", "2", "January - June 2024", "start"), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) + ), + class_name=chart_tooltip_content(1, "line") + " w-full p-0", ) ``` @@ -499,53 +619,88 @@ Implements a user-defined legend layout for better presentation control. ```python def linechart_v8(): - data = [ - {"month": "Jan", "desktop": 186, "mobile": 80}, - {"month": "Feb", "desktop": 305, "mobile": 200}, - {"month": "Mar", "desktop": 237, "mobile": 120}, - {"month": "Apr", "desktop": 73, "mobile": 190}, - {"month": "May", "desktop": 209, "mobile": 130}, - {"month": "Jun", "desktop": 214, "mobile": 140}, - ] - return rx.box( - rx.hstack( - rx.foreach( - [["Desktop", "var(--chart-1)"], ["Mobile", "var(--chart-2)"]], - lambda key: rx.hstack( - rx.box(class_name="w-3 h-3 rounded-sm", bg=key[1]), - rx.text( - key[0], - class_name="text-sm font-semibold", - color=rx.color("slate", 11), - ), - align="center", - spacing="2", + + return card.root( + card.header( + rx.hstack( + rx.el.div( + card.title("Line Chart - Multiple"), + card.description("Showing total visitors for the last 6 months"), + class_name="flex flex-col gap-y-1.5", ), + align="center", + justify="between", + width="100%", ), - class_name="py-4 px-4 flex w-full flex justify-center gap-8", ), - rx.recharts.line_chart( - get_tooltip(), - rx.recharts.line( - data_key="desktop", - stroke="var(--chart-1)", - type_="linear", - dot=False, - stroke_width=2, + card.content( + rx.recharts.line_chart( + chart_tooltip(), + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.line( + data_key="desktop", + stroke="var(--chart-1)", + stroke_width=2, + type_="linear", + dot=False, + is_animation_active=False, + active_dot={"fill": "var(--chart-1)"}, + ), + rx.recharts.line( + data_key="mobile", + stroke="var(--chart-2)", + stroke_width=2, + type_="linear", + dot=False, + is_animation_active=False, + active_dot={"fill": "var(--chart-2)"}, + ), + rx.recharts.x_axis( + data_key="month", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + interval="preserveStartEnd", + ), + data=data, + width="100%", + height=210, ), - rx.recharts.line( - data_key="mobile", - stroke="var(--chart-2)", - type_="linear", - dot=False, - stroke_width=2, + rx.el.div( + rx.foreach( + ["Desktop", "Mobile"], + lambda device, index: rx.el.div( + rx.el.div( + class_name=f"w-3 h-3 rounded-sm bg-chart-{index + 1}" + ), + rx.el.p(device, class_name="text-sm text-foreground"), + class_name="flex flex-row items-center gap-x-2", + ), + ), + class_name="py-4 px-4 flex w-full flex justify-center gap-8", ), - get_x_axis("month"), - data=data, - width="100%", - height=250, + class_name="flex flex-col h-[250px]", + ), + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 [&_.recharts-tooltip-item-separator]:w-full", + class_name=chart_tooltip_content(2, "square") + " w-full p-0", ) ``` diff --git a/assets/docs/charts/pie-chart.md b/assets/docs/charts/pie-chart.md index 62c195b6..79d9635a 100644 --- a/assets/docs/charts/pie-chart.md +++ b/assets/docs/charts/pie-chart.md @@ -4,79 +4,6 @@ Pie Charts are ideal for showing changes over time or the magnitude of multiple datasets stacked together. They combine the smoothness of line charts with the visual impact of filled areas. -# Usage -Copy the following helper functions into your Reflex application. - - -```python -import reflex as rx - -tooltip = { - "is_animation_active": False, - "separator": "", - "cursor": False, - "item_style": { - "color": "currentColor", - "display": "flex", - "paddingBottom": "0px", - "justifyContent": "space-between", - "textTransform": "capitalize", - }, - "label_style": { - "color": rx.color("slate", 10), - "fontWeight": "500", - }, - "content_style": { - "background": rx.color_mode_cond("oklch(0.97 0.00 0)", "oklch(0.14 0.00 286)"), - "borderColor": rx.color("slate", 5), - "borderRadius": "5px", - "fontFamily": "IBM Plex Mono,ui-monospace,monospace", - "fontSize": "0.875rem", - "lineHeight": "1.25rem", - "fontWeight": "500", - "letterSpacing": "-0.01rem", - "minWidth": "8rem", - "width": "175px", - "padding": "0.375rem 0.625rem ", - "position": "relative", - }, -} - - -def info(title: str, size: str, subtitle: str, align: str): - return rx.vstack( - rx.heading(title, size=size, weight="bold"), - rx.text(subtitle, size="1", color=rx.color("slate", 11), weight="medium"), - spacing="1", - align=align, - ) - - -def get_tooltip(): - """Standard tooltip for all charts.""" - return rx.recharts.graphing_tooltip(**tooltip) - - -def get_cartesian_grid(): - """Standard cartesian grid for charts.""" - return rx.recharts.cartesian_grid( - horizontal=True, vertical=False, class_name="opacity-25" - ) - - -def get_x_axis(data_key: str): - """Standard X axis configuration.""" - return rx.recharts.x_axis( - data_key=data_key, - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", - ) -``` - - # Examples Below are examples demonstrating how these components and charts can be used. @@ -85,40 +12,49 @@ A minimal example showing proportional data distribution in a circular format. ```python def piechart_v1(): - data = [ - {"browser": "chrome", "visitors": 275}, - {"browser": "safari", "visitors": 200}, - {"browser": "firefox", "visitors": 187}, - {"browser": "edge", "visitors": 173}, - {"browser": "other", "visitors": 90}, - ] - return rx.box( - info("Pie Chart", "3", "January - June 2024", "center"), - rx.recharts.pie_chart( - get_tooltip(), - rx.recharts.pie( - rx.foreach( - range(6), - lambda color, index: rx.recharts.cell( - fill=f"var(--chart-{index + 1})", + return card.root( + card.header( + card.title("Pie Chart"), + card.description("Browser distribution - Last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + is_animation_active=False, ), - data=data, - data_key="visitors", - name_key="browser", - stroke="0", + width="100%", + height=250, ), - width="100%", - height=250, ), - info( - "Trending up by 5.2% this month", - "2", - "Showing total visitors for the last 6 months", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center [&_.recharts-tooltip-item-separator]:w-full", + class_name="w-full p-0", ) ``` @@ -128,43 +64,50 @@ Displays labels that appear dynamically when hovering over chart segments. ```python def piechart_v2(): - data = [ - {"browser": "chrome", "visitors": 275}, - {"browser": "safari", "visitors": 200}, - {"browser": "firefox", "visitors": 187}, - {"browser": "edge", "visitors": 173}, - {"browser": "other", "visitors": 90}, - ] - return rx.box( - info("Pie Chart - Hovering Labels", "3", "January - June 2024", "center"), - rx.recharts.pie_chart( - get_tooltip(), - rx.recharts.pie( - rx.foreach( - range(6), - lambda color, index: rx.recharts.cell( - fill=f"var(--chart-{index + 1})", + return card.root( + card.header( + card.title("Pie Chart - Hovering Labels"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + label=True, + is_animation_active=False, ), - data=data, - data_key="visitors", - name_key="browser", - stroke="0", - label=True, - label_line=False, - class_name="text-sm font-bold", + width="100%", + height=250, ), - width="100%", - height=250, ), - info( - "Trending up by 5.2% this month", - "2", - "Showing total visitors for the last 6 months", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center [&_.recharts-tooltip-item-separator]:w-full", + class_name="w-full p-0", ) ``` @@ -174,44 +117,55 @@ Shows labels positioned inside each pie segment for compact presentation. ```python def piechart_v3(): - data = [ - {"browser": "chrome", "visitors": 275}, - {"browser": "safari", "visitors": 200}, - {"browser": "firefox", "visitors": 187}, - {"browser": "edge", "visitors": 173}, - {"browser": "other", "visitors": 90}, - ] - return rx.box( - info("Pie Chart - Inner Labels", "3", "January - June 2024", "center"), - rx.recharts.pie_chart( - get_tooltip(), - rx.recharts.pie( - rx.recharts.label_list( - fill=rx.color("slate", 12), - class_name="text-sm font-bold", - ), - rx.foreach( - range(6), - lambda color, index: rx.recharts.cell( - fill=f"var(--chart-{index + 1})", + return card.root( + card.header( + card.title("Pie Chart - Inner Labels"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.recharts.label_list( + data_key="browser", + position="inside", + fill="var(--background)", + custom_attrs={"fontSize": "12px", "fontWeight": "bold"}, ), + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), + ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + is_animation_active=False, ), - data=data, - data_key="visitors", - name_key="browser", - stroke="0", + width="100%", + height=250, ), - width="100%", - height=250, ), - info( - "Trending up by 5.2% this month", - "2", - "Showing total visitors for the last 6 months", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center [&_.recharts-tooltip-item-separator]:w-full", + class_name="w-full p-0", ) ``` @@ -221,42 +175,50 @@ Adds a built-in legend for easy segment identification and reference. ```python def piechart_v4(): - data = [ - {"browser": "chrome", "visitors": 275}, - {"browser": "safari", "visitors": 200}, - {"browser": "firefox", "visitors": 187}, - {"browser": "edge", "visitors": 173}, - {"browser": "other", "visitors": 90}, - ] - return rx.box( - info("Pie Chart - Legend", "3", "January - June 2024", "center"), - rx.recharts.pie_chart( - get_tooltip(), - rx.recharts.pie( - rx.foreach( - range(6), - lambda color, index: rx.recharts.cell( - fill=f"var(--chart-{index + 1})", + return card.root( + card.header( + card.title("Pie Chart - Legend"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + is_animation_active=False, ), - data=data, - data_key="visitors", - name_key="browser", - stroke="0", - legend_type="square", + rx.recharts.legend(class_name="text-xs font-medium"), + width="100%", + height=250, ), - rx.recharts.legend(class_name="text-sm font-bold"), - width="100%", - height=250, ), - info( - "Trending up by 5.2% this month", - "2", - "Showing total visitors for the last 6 months", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center [&_.recharts-tooltip-item-separator]:w-full", + class_name="w-full p-0", ) ``` @@ -266,41 +228,50 @@ Renders the pie chart with a hollow center for a modern doughnut style. ```python def piechart_v5(): - data = [ - {"browser": "chrome", "visitors": 275}, - {"browser": "safari", "visitors": 200}, - {"browser": "firefox", "visitors": 187}, - {"browser": "edge", "visitors": 173}, - {"browser": "other", "visitors": 90}, - ] - return rx.box( - info("Pie Chart - Doughnut", "3", "January - June 2024", "center"), - rx.recharts.pie_chart( - get_tooltip(), - rx.recharts.pie( - rx.foreach( - range(6), - lambda color, index: rx.recharts.cell( - fill=f"var(--chart-{index + 1})", + return card.root( + card.header( + card.title("Pie Chart - Doughnut"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + inner_radius=60, + is_animation_active=False, ), - data=data, - data_key="visitors", - name_key="browser", - stroke="0", - inner_radius=60, + width="100%", + height=250, ), - width="100%", - height=250, ), - info( - "Trending up by 5.2% this month", - "2", - "Showing total visitors for the last 6 months", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center [&_.recharts-tooltip-item-separator]:w-full", + class_name="w-full p-0", ) ``` @@ -310,46 +281,54 @@ Demonstrates interactive segment highlighting and selection states. ```python def piechart_v6(): - data = [ - {"browser": "chrome", "visitors": 275}, - {"browser": "safari", "visitors": 200}, - {"browser": "firefox", "visitors": 187}, - {"browser": "edge", "visitors": 173}, - {"browser": "other", "visitors": 90}, - ] - return rx.box( - info("Pie Chart - Active", "3", "January - June 2024", "center"), - rx.recharts.pie_chart( - get_tooltip(), - rx.recharts.pie( - rx.foreach( - range(6), - lambda color, index: rx.recharts.cell( - fill=f"var(--chart-{index + 1})", + return card.root( + card.header( + card.title("Pie Chart - Active"), + card.description("Showing total visitors for the last 6 months"), + ), + card.content( + rx.recharts.pie_chart( + rx.recharts.pie( + rx.foreach( + range(5), + lambda color, index: rx.recharts.cell( + fill=f"var(--chart-{index + 1})", + ), ), + data=data, + data_key="visitors", + name_key="browser", + stroke_width=2, + stroke="var(--background)", + inner_radius=60, + custom_attrs={ + "activeIndex": 1, + "activeShape": {"outerRadius": 110}, + }, + is_animation_active=False, ), - data=data, - data_key="visitors", - name_key="browser", - stroke="0", - inner_radius=60, - custom_attrs={ - "strokeWidth": 5, - "activeIndex": 1, - "activeShape": {"outerRadius": 120}, - }, + width="100%", + height=250, ), - width="100%", - height=250, ), - info( - "Trending up by 5.2% this month", - "2", - "Showing total visitors for the last 6 months", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center [&_.recharts-tooltip-item-separator]:w-full", + class_name="w-full p-0", ) ``` diff --git a/assets/docs/charts/radar-chart.md b/assets/docs/charts/radar-chart.md index 4076fbca..aafb3c5d 100644 --- a/assets/docs/charts/radar-chart.md +++ b/assets/docs/charts/radar-chart.md @@ -4,79 +4,6 @@ Radar Charts are ideal for showing changes over time or the magnitude of multiple datasets stacked together. They combine the smoothness of line charts with the visual impact of filled areas. -# Usage -Copy the following helper functions into your Reflex application. - - -```python -import reflex as rx - -tooltip = { - "is_animation_active": False, - "separator": "", - "cursor": False, - "item_style": { - "color": "currentColor", - "display": "flex", - "paddingBottom": "0px", - "justifyContent": "space-between", - "textTransform": "capitalize", - }, - "label_style": { - "color": rx.color("slate", 10), - "fontWeight": "500", - }, - "content_style": { - "background": rx.color_mode_cond("oklch(0.97 0.00 0)", "oklch(0.14 0.00 286)"), - "borderColor": rx.color("slate", 5), - "borderRadius": "5px", - "fontFamily": "IBM Plex Mono,ui-monospace,monospace", - "fontSize": "0.875rem", - "lineHeight": "1.25rem", - "fontWeight": "500", - "letterSpacing": "-0.01rem", - "minWidth": "8rem", - "width": "175px", - "padding": "0.375rem 0.625rem ", - "position": "relative", - }, -} - - -def info(title: str, size: str, subtitle: str, align: str): - return rx.vstack( - rx.heading(title, size=size, weight="bold"), - rx.text(subtitle, size="1", color=rx.color("slate", 11), weight="medium"), - spacing="1", - align=align, - ) - - -def get_tooltip(): - """Standard tooltip for all charts.""" - return rx.recharts.graphing_tooltip(**tooltip) - - -def get_cartesian_grid(): - """Standard cartesian grid for charts.""" - return rx.recharts.cartesian_grid( - horizontal=True, vertical=False, class_name="opacity-25" - ) - - -def get_x_axis(data_key: str): - """Standard X axis configuration.""" - return rx.recharts.x_axis( - data_key=data_key, - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", - ) -``` - - # Examples Below are examples demonstrating how these components and charts can be used. @@ -85,53 +12,48 @@ A minimal example showing multivariate data in a radial layout with filled areas ```python def radar_v1(): - stats = [ - {"category": "Farming", "score": 8}, - {"category": "Fighting", "score": 7}, - {"category": "Aggressiveness", "score": 6}, - {"category": "Map Awareness", "score": 5}, - {"category": "Objective Control", "score": 9}, - {"category": "Positioning", "score": 7}, - ] - return rx.box( - info( - "Radar Chart", - "3", - "Player performance across key gameplay categories", - "center", + return card.root( + card.header( + card.title("Radar Chart"), + card.description("Player performance across categories"), ), - rx.recharts.radar_chart( - rx.recharts.polar_grid( - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid(class_name="opacity-30"), + rx.recharts.polar_angle_axis( + data_key="category", + custom_attrs={"fontSize": "12px"}, ), - ), - rx.recharts.polar_angle_axis( - data_key="category", - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + rx.recharts.radar( + data_key="score", + stroke="var(--chart-1)", + fill="var(--chart-1)", + fill_opacity=0.6, + is_animation_active=False, ), + data=stats_data, + width="100%", + height=250, ), - rx.recharts.radar( - data_key="score", - stroke="none", - fill="var(--chart-1)", - ), - data=stats, - width="100%", - height=250, - margin={"left": 20, "right": 20}, ), - info( - "Trending up by 5.2% this month", - "2", - "Performance trends in key gameplay categories", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center", + class_name="w-full p-0", ) ``` @@ -141,55 +63,49 @@ Displays data points with visible markers along the radar lines for clarity. ```python def radar_v2(): - stats = [ - {"category": "Farming", "score": 8}, - {"category": "Fighting", "score": 7}, - {"category": "Aggressiveness", "score": 6}, - {"category": "Map Awareness", "score": 5}, - {"category": "Objective Control", "score": 9}, - {"category": "Positioning", "score": 7}, - ] - return rx.box( - info( - "Radar Chart - Dots", - "3", - "Player performance across key gameplay categories", - "center", + return card.root( + card.header( + card.title("Radar Chart - Dots"), + card.description("Detailed performance metrics"), ), - rx.recharts.radar_chart( - rx.recharts.polar_grid( - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid(class_name="opacity-30"), + rx.recharts.polar_angle_axis( + data_key="category", + custom_attrs={"fontSize": "12px"}, ), - ), - rx.recharts.polar_angle_axis( - data_key="category", - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + rx.recharts.radar( + data_key="score", + dot=True, + stroke="var(--chart-1)", + fill="var(--chart-1)", + fill_opacity=0.6, + is_animation_active=False, ), + data=stats_data, + width="100%", + height=250, ), - rx.recharts.radar( - data_key="score", - dot=True, - stroke="var(--chart-2)", - fill="var(--chart-1)", - custom_attrs={"strokeWidth": 2}, - ), - data=stats, - width="100%", - height=250, - margin={"left": 20, "right": 20}, ), - info( - "Trending up by 5.2% this month", - "2", - "Performance trends in key gameplay categories", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center", + class_name="w-full p-0", ) ``` @@ -199,58 +115,55 @@ Visualizes multiple data series layered on top of each other for comparison. ```python def radar_v3(): - stats = [ - {"category": "Farming", "score": 6, "average": 8}, - {"category": "Fighting", "score": 8, "average": 9}, - {"category": "Aggressiveness", "score": 5, "average": 8}, - {"category": "Map Awareness", "score": 9, "average": 9}, - {"category": "Objective Control", "score": 7, "average": 8}, - {"category": "Positioning", "score": 6, "average": 9}, - ] - return rx.vstack( - info( - "Radar Chart - Stacked", - "3", - "Player performance across key gameplay categories", - "center", + return card.root( + card.header( + card.title("Radar Chart - Stacked"), + card.description("Comparing score against average"), ), - rx.recharts.radar_chart( - get_tooltip(), - rx.recharts.polar_grid( - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid(class_name="opacity-30"), + rx.recharts.polar_angle_axis( + data_key="category", + custom_attrs={"fontSize": "12px"}, ), - ), - rx.recharts.polar_angle_axis( - data_key="category", - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + rx.recharts.radar( + data_key="average", + stroke="var(--chart-2)", + fill="var(--chart-2)", + fill_opacity=0.4, + is_animation_active=False, ), + rx.recharts.radar( + data_key="score", + stroke="var(--chart-1)", + fill="var(--chart-1)", + fill_opacity=0.7, + is_animation_active=False, + ), + data=stats_data, + width="100%", + height=250, ), - rx.recharts.radar( - data_key="average", fill_opacity=0.5, stroke="none", fill="teal" - ), - rx.recharts.radar( - data_key="score", - fill_opacity=1.0, - stroke="none", - fill="var(--chart-1)", - ), - data=stats, - width="100%", - height=250, - margin={"left": 20, "right": 20}, ), - info( - "Trending up by 5.2% this month", - "2", - "Performance trends in key gameplay categories", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center [&_.recharts-tooltip-item-separator]:w-full", + class_name="w-full p-0", ) ``` @@ -260,61 +173,55 @@ Shows only the outline strokes without filled areas for a cleaner look. ```python def radar_v4(): - stats = [ - {"category": "Farming", "score": 8, "average": 8}, - {"category": "Fighting", "score": 7, "average": 9}, - {"category": "Aggressiveness", "score": 6, "average": 8}, - {"category": "Map Awareness", "score": 5, "average": 9}, - {"category": "Objective Control", "score": 9, "average": 8}, - {"category": "Positioning", "score": 7, "average": 9}, - ] - return rx.vstack( - info( - "Radar Chart - Lines Only", - "3", - "Player performance across key gameplay categories", - "center", + return card.root( + card.header( + card.title("Radar Chart - Lines Only"), + card.description("Linear comparison between score and average"), ), - rx.recharts.radar_chart( - get_tooltip(), - rx.recharts.polar_grid( - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid(class_name="opacity-30"), + rx.recharts.polar_angle_axis( + data_key="category", + custom_attrs={"fontSize": "12px"}, ), - ), - rx.recharts.polar_angle_axis( - data_key="category", - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + rx.recharts.radar( + data_key="average", + stroke="var(--chart-2)", + fill="none", + stroke_width=2, + is_animation_active=False, ), + rx.recharts.radar( + data_key="score", + stroke="var(--chart-1)", + fill="none", + stroke_width=2, + is_animation_active=False, + ), + data=stats_data, + width="100%", + height=250, ), - rx.recharts.radar( - data_key="average", - stroke="teal", - fill="none", - custom_attrs={"strokeWidth": 2}, - ), - rx.recharts.radar( - data_key="score", - fill="none", - stroke="var(--chart-1)", - custom_attrs={"strokeWidth": 2}, - ), - data=stats, - width="100%", - height=250, - margin={"left": 20, "right": 20}, ), - info( - "Trending up by 5.2% this month", - "2", - "Performance trends in key gameplay categories", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center [&_.recharts-tooltip-item-separator]:w-full", + class_name="w-full p-0", ) ``` @@ -324,58 +231,49 @@ Uses circular grid lines instead of polygon shapes for the background. ```python def radar_v5(): - stats = [ - {"category": "Farming", "score": 8}, - {"category": "Fighting", "score": 7}, - {"category": "Aggressiveness", "score": 6}, - {"category": "Map Awareness", "score": 5}, - {"category": "Objective Control", "score": 9}, - {"category": "Positioning", "score": 7}, - ] - return rx.box( - info( - "Radar Chart - Circle Grid", - "3", - "Player performance across key gameplay categories", - "center", + return card.root( + card.header( + card.title("Radar Chart - Circle Grid"), + card.description("Performance visual with circular boundaries"), ), - rx.recharts.radar_chart( - get_tooltip(), - rx.recharts.polar_grid( - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid(grid_type="circle", class_name="opacity-30"), + rx.recharts.polar_angle_axis( + data_key="category", + custom_attrs={"fontSize": "12px"}, ), - grid_type="circle", - ), - rx.recharts.polar_angle_axis( - data_key="category", - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + rx.recharts.radar( + data_key="score", + dot=True, + stroke="var(--chart-1)", + fill="var(--chart-1)", + fill_opacity=0.6, + is_animation_active=False, ), - axis_line_type="circle", - ), - rx.recharts.radar( - data_key="score", - dot=True, - stroke="var(--chart-2)", - fill="var(--chart-1)", - custom_attrs={"strokeWidth": 2}, + data=stats_data, + width="100%", + height=250, ), - data=stats, - width="100%", - height=250, - margin={"left": 20, "right": 20}, ), - info( - "Trending up by 5.2% this month", - "2", - "Performance trends in key gameplay categories", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center [&_.recharts-tooltip-item-separator]:w-full", + class_name="w-full p-0", ) ``` @@ -385,57 +283,52 @@ Renders the grid with filled background sections for enhanced visual contrast. ```python def radar_v6(): - stats = [ - {"category": "Farming", "score": 7}, - {"category": "Fighting", "score": 6}, - {"category": "Aggressiveness", "score": 7}, - {"category": "Map Awareness", "score": 6}, - {"category": "Objective Control", "score": 6}, - {"category": "Positioning", "score": 7}, - ] - return rx.box( - info( - "Radar Chart - Filled Grid", - "3", - "Player performance across key gameplay categories", - "center", + return card.root( + card.header( + card.title("Radar Chart - Filled Grid"), + card.description("Player performance across categories"), ), - rx.recharts.radar_chart( - get_tooltip(), - rx.recharts.polar_grid( - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300 fill-[gray] opacity-20", - "text-sm stroke-gray-700 fill-[gray] opacity-20", + card.content( + rx.recharts.radar_chart( + rx.recharts.polar_grid( + grid_type="circle", + class_name="opacity-20 fill-primary stroke-input", ), - grid_type="circle", - ), - rx.recharts.polar_angle_axis( - data_key="category", - class_name=rx.color_mode_cond( - "text-sm stroke-gray-300", - "text-sm stroke-gray-700", + rx.recharts.polar_angle_axis( + data_key="category", + axis_line_type="circle", + class_name="!text-xs stroke-input", ), - axis_line_type="circle", - ), - rx.recharts.radar( - data_key="score", - dot=False, - fill="var(--chart-1)", - stroke="none", + rx.recharts.radar( + data_key="score", + dot=False, + fill="white", + stroke="none", + is_animation_active=False, + ), + data=stats_data, + width="100%", + height=250, ), - data=stats, - width="100%", - height=250, - margin={"left": 20, "right": 20}, ), - info( - "Trending up by 5.2% this month", - "2", - "Performance trends in key gameplay categories", - "center", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="w-full flex flex-col gap-y-4 p-1 items-center [&_.recharts-tooltip-item-separator]:w-full", + class_name="w-full p-0", ) ``` diff --git a/assets/docs/charts/scatter-chart.md b/assets/docs/charts/scatter-chart.md index a55e6da5..1e293e51 100644 --- a/assets/docs/charts/scatter-chart.md +++ b/assets/docs/charts/scatter-chart.md @@ -4,79 +4,6 @@ Scatter Charts are ideal for showing changes over time or the magnitude of multiple datasets stacked together. They combine the smoothness of line charts with the visual impact of filled areas. -# Usage -Copy the following helper functions into your Reflex application. - - -```python -import reflex as rx - -tooltip = { - "is_animation_active": False, - "separator": "", - "cursor": False, - "item_style": { - "color": "currentColor", - "display": "flex", - "paddingBottom": "0px", - "justifyContent": "space-between", - "textTransform": "capitalize", - }, - "label_style": { - "color": rx.color("slate", 10), - "fontWeight": "500", - }, - "content_style": { - "background": rx.color_mode_cond("oklch(0.97 0.00 0)", "oklch(0.14 0.00 286)"), - "borderColor": rx.color("slate", 5), - "borderRadius": "5px", - "fontFamily": "IBM Plex Mono,ui-monospace,monospace", - "fontSize": "0.875rem", - "lineHeight": "1.25rem", - "fontWeight": "500", - "letterSpacing": "-0.01rem", - "minWidth": "8rem", - "width": "175px", - "padding": "0.375rem 0.625rem ", - "position": "relative", - }, -} - - -def info(title: str, size: str, subtitle: str, align: str): - return rx.vstack( - rx.heading(title, size=size, weight="bold"), - rx.text(subtitle, size="1", color=rx.color("slate", 11), weight="medium"), - spacing="1", - align=align, - ) - - -def get_tooltip(): - """Standard tooltip for all charts.""" - return rx.recharts.graphing_tooltip(**tooltip) - - -def get_cartesian_grid(): - """Standard cartesian grid for charts.""" - return rx.recharts.cartesian_grid( - horizontal=True, vertical=False, class_name="opacity-25" - ) - - -def get_x_axis(data_key: str): - """Standard X axis configuration.""" - return rx.recharts.x_axis( - data_key=data_key, - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - interval="preserveStartEnd", - ) -``` - - # Examples Below are examples demonstrating how these components and charts can be used. @@ -86,71 +13,79 @@ A minimal example showing the relationship between two variables with individual ```python def scatterchart_v1(): - scat = [ - {"name": " ", "x": 10, "y": 30}, - {"name": "B", "x": 20, "y": 50}, - {"name": "C", "x": 30, "y": 70}, - {"name": "D", "x": 40, "y": 20}, - {"name": "E", "x": 50, "y": 90}, - {"name": "F", "x": 60, "y": 40}, - {"name": "G", "x": 70, "y": 60}, - {"name": "H", "x": 80, "y": 100}, - {"name": "I", "x": 90, "y": 10}, - {"name": " ", "x": 100, "y": 80}, - ] - scat_2 = [ - {"name": "K", "x": 5, "y": 15}, - {"name": "L", "x": 15, "y": 40}, - {"name": "M", "x": 25, "y": 60}, - {"name": "N", "x": 35, "y": 10}, - {"name": "O", "x": 45, "y": 80}, - {"name": "P", "x": 55, "y": 30}, - {"name": "Q", "x": 65, "y": 50}, - {"name": "R", "x": 75, "y": 90}, - {"name": "S", "x": 85, "y": 20}, - {"name": "T", "x": 95, "y": 70}, - ] - return rx.el.div( - rx.hstack( - rx.foreach( - [ - ["Data 1", "var(--chart-1)"], - ["Data 2", "var(--chart-2)"], - ], - lambda key: rx.hstack( - rx.box(class_name="w-3 h-3 rounded-sm", bg=key[1]), - rx.text( - key[0], - class_name="text-sm font-semibold", - color=rx.color("slate", 11), + return card.root( + card.header( + rx.el.div( + card.title("Scatter Chart"), + card.description("Showing multi-series distribution"), + class_name="flex flex-col gap-y-1.5", + ) + ), + card.content( + rx.recharts.scatter_chart( + rx.recharts.cartesian_grid( + horizontal=True, vertical=False, class_name="opacity-30" + ), + rx.recharts.scatter( + name="Data 1", + data=scat_data_1, + fill="var(--chart-1)", + is_animation_active=False, + ), + rx.recharts.scatter( + name="Data 2", + data=scat_data_2, + fill="var(--chart-2)", + is_animation_active=False, + ), + rx.recharts.y_axis( + data_key="y", + hide=True, + ), + rx.recharts.x_axis( + data_key="x", + type_="number", + axis_line=False, + tick_size=10, + tick_line=False, + custom_attrs={"fontSize": "12px"}, + ), + width="100%", + height=210, + ), + rx.el.div( + rx.foreach( + ["Data 1", "Data 2"], + lambda data, index: rx.el.div( + rx.el.div( + class_name=f"h-3 w-3 rounded-sm bg-chart-{index + 1}" + ), + rx.el.div(data, class_name="text-xs text-foreground"), + class_name="flex flex-row items-center gap-x-2", ), - align="center", - spacing="2", ), + class_name="flex items-center gap-4", ), - class_name="py-4 px-4 flex w-full flex justify-center gap-8", + class_name="flex flex-col h-[250px] items-center justify-center", ), - rx.recharts.scatter_chart( - rx.recharts.scatter(data=scat, data_key="name", fill="var(--chart-1)"), - rx.recharts.scatter(data=scat_2, data_key="name", fill="var(--chart-2)"), - rx.recharts.y_axis( - data_key="y", - hide=True, - ), - rx.recharts.x_axis( - data_key="x", - type_="number", - axis_line=False, - tick_size=10, - tick_line=False, - custom_attrs={"fontSize": "12px"}, - ), - width="100%", - height=350, - class_name="px-4", + card.footer( + rx.el.div( + rx.el.div( + rx.el.div( + "Trending up by 5.2% this month ", + class_name="flex items-center gap-2 leading-none font-medium", + ), + rx.el.div( + "January - June 2024", + class_name="flex items-center gap-2 leading-none text-muted-foreground", + ), + class_name="grid gap-2", + ), + class_name="flex w-full items-start gap-2 text-sm", + ) ), - class_name="flex flex-col size-full", + class_name="w-full p-0", ) ``` diff --git a/assets/docs/components/accordion.md b/assets/docs/components/accordion.md index b7bfd4c2..b0110310 100644 --- a/assets/docs/components/accordion.md +++ b/assets/docs/components/accordion.md @@ -8,11 +8,10 @@ A set of collapsible panels with headings. Copy the following code into your app directory. - ### CLI ```bash -buridan add component accordion +uv run buridan add component accordion ``` ### Manual Installation @@ -23,15 +22,16 @@ buridan add component accordion from typing import Any, Literal from reflex.components.component import Component, ComponentNamespace -from reflex.components.core.foreach import foreach -from reflex.components.el import Div from reflex.event import EventHandler, passthrough_event_spec from reflex.utils.imports import ImportVar from reflex.vars.base import Var from reflex.vars.object import ObjectVar +from reflex_components_core.core.foreach import foreach +from reflex_components_core.el import Div -from ..base_ui import PACKAGE_NAME, BaseUIComponent -from ...icons.hugeicon import icon +from ..icons.hugeicon import hi, icon +from .base_ui import PACKAGE_NAME, BaseUIComponent +from .button import button LiteralOrientation = Literal["horizontal", "vertical"] @@ -41,16 +41,12 @@ ITEMS_TYPE = list[dict[str, str | Component]] class ClassNames: """Class names for accordion components.""" - ROOT = "flex flex-col justify-center overflow-hidden rounded-xl" - ITEM = "" + ROOT = "flex w-full flex-col divide-y divide-input" + ITEM = "not-last:border-b" HEADER = "" - TRIGGER = "group relative flex w-full items-center justify-between gap-4 px-4 py-2 text-md font-semibold" - PANEL = ( - "overflow-hidden text-sm text-foreground font-medium " - "transition-all duration-300 ease-out " - "data-[ending-style]:h-0 data-[starting-style]:h-0" - ) - PANEL_DIV = "py-2 px-4" + TRIGGER = "group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring disabled:pointer-events-none disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground" + PANEL = "h-[var(--accordion-panel-height)] overflow-hidden transition-[height] ease-out data-[ending-style]:h-0 data-[starting-style]:h-0" + PANEL_DIV = "" TRIGGER_ICON = "size-4 shrink-0 transition-all ease-out group-data-[panel-open]:scale-110 group-data-[panel-open]:rotate-45" @@ -83,13 +79,13 @@ class AccordionRoot(AccordionBaseComponent): hidden_until_found: Var[bool] # Whether multiple items can be open at the same time. Defaults to True. - open_multiple: Var[bool] + multiple: Var[bool] # Whether the component should ignore user interaction. Defaults to False. disabled: Var[bool] # Whether to loop keyboard focus back to the first item when the end of the list is reached while using the arrow keys. Defaults to True. - loop: Var[bool] + loop_focus: Var[bool] # The visual orientation of the accordion. Controls whether roving focus uses left/right or up/down arrow keys. Defaults to 'vertical'. orientation: Var[LiteralOrientation] @@ -154,17 +150,43 @@ class AccordionTrigger(AccordionBaseComponent): tag = "Accordion.Trigger" - # Whether the component renders a native `