From dbd074c33dd77c39ee7776bcfc9faef35d4fa0c2 Mon Sep 17 00:00:00 2001 From: Lendemor Date: Mon, 26 Feb 2024 17:18:28 +0100 Subject: [PATCH] add docs from reflex-web --- docs/__init__.py | 0 docs/api-reference/browser_javascript.md | 214 +++++++ docs/api-reference/browser_storage.md | 191 ++++++ docs/api-reference/cli.md | 90 +++ docs/api-reference/event_triggers.md | 263 ++++++++ docs/api-reference/special_events.md | 93 +++ docs/api-routes/overview.md | 43 ++ docs/assets/referencing_assets.md | 42 ++ docs/assets/upload_and_download_files.md | 89 +++ docs/client_storage/overview.md | 34 + docs/components/conditional_props.md | 25 + docs/components/conditional_rendering.md | 269 ++++++++ docs/components/props.md | 81 +++ docs/components/rendering_iterables.md | 197 ++++++ docs/components/style_props.md | 25 + docs/database/overview.md | 75 +++ docs/database/queries.md | 186 ++++++ docs/database/relationships.md | 162 +++++ docs/database/tables.md | 70 +++ docs/datatable_tutorial/__init__.py | 0 docs/datatable_tutorial/add_interactivity.md | 223 +++++++ docs/datatable_tutorial/add_styling.md | 141 +++++ .../datatable_tutorial_utils.py | 328 ++++++++++ docs/datatable_tutorial/live_stream.md | 108 ++++ docs/datatable_tutorial/simple_table.md | 87 +++ docs/events/background_events.md | 175 ++++++ docs/events/chaining_events.md | 90 +++ docs/events/event_arguments.md | 34 + docs/events/events_overview.md | 41 ++ docs/events/page_load_events.md | 21 + docs/events/setters.md | 52 ++ docs/events/special_events.md | 20 + docs/events/yield_events.md | 63 ++ docs/getting-started/configuration.md | 91 +++ docs/getting-started/installation.md | 147 +++++ docs/getting-started/introduction.md | 226 +++++++ docs/getting-started/project-structure.md | 66 ++ docs/hosting/deploy-quick-start.md | 69 +++ docs/hosting/hosting-cli-commands.md | 354 +++++++++++ docs/hosting/self-hosting.md | 121 ++++ docs/library/chakra/datadisplay/badge.md | 44 ++ docs/library/chakra/datadisplay/divider.md | 34 + docs/library/chakra/datadisplay/list.md | 68 ++ docs/library/chakra/datadisplay/stat.md | 41 ++ docs/library/chakra/datadisplay/table.md | 142 +++++ docs/library/chakra/disclosure/accordion.md | 103 ++++ docs/library/chakra/disclosure/tabs.md | 48 ++ docs/library/chakra/feedback/alert.md | 70 +++ .../chakra/feedback/circularprogress.md | 26 + docs/library/chakra/feedback/progress.md | 24 + docs/library/chakra/feedback/skeleton.md | 54 ++ docs/library/chakra/feedback/spinner.md | 38 ++ docs/library/chakra/forms/button.md | 375 +++++++++++ docs/library/chakra/forms/buttongroup.md | 150 +++++ docs/library/chakra/forms/checkbox.md | 69 +++ docs/library/chakra/forms/editable.md | 48 ++ docs/library/chakra/forms/form.md | 110 ++++ docs/library/chakra/forms/formcontrol.md | 51 ++ docs/library/chakra/forms/input.md | 110 ++++ docs/library/chakra/forms/numberinput.md | 28 + docs/library/chakra/forms/pininput.md | 45 ++ docs/library/chakra/forms/radiogroup.md | 74 +++ docs/library/chakra/forms/rangeslider.md | 45 ++ docs/library/chakra/forms/select.md | 86 +++ docs/library/chakra/forms/slider.md | 100 +++ docs/library/chakra/forms/switch.md | 48 ++ docs/library/chakra/forms/textarea.md | 30 + docs/library/chakra/layout/aspectratio.md | 27 + docs/library/chakra/layout/box.md | 46 ++ docs/library/chakra/layout/card.md | 25 + docs/library/chakra/layout/center.md | 43 ++ docs/library/chakra/layout/container.md | 20 + docs/library/chakra/layout/flex.md | 33 + docs/library/chakra/layout/grid.md | 43 ++ docs/library/chakra/layout/responsivegrid.md | 39 ++ docs/library/chakra/layout/spacer.md | 23 + docs/library/chakra/layout/stack.md | 36 ++ docs/library/chakra/layout/wrap.md | 27 + docs/library/chakra/media/avatar.md | 62 ++ docs/library/chakra/media/icon.md | 44 ++ docs/library/chakra/media/image.md | 54 ++ docs/library/chakra/navigation/breadcrumb.md | 36 ++ docs/library/chakra/navigation/link.md | 41 ++ docs/library/chakra/navigation/linkoverlay.md | 18 + docs/library/chakra/overlay/alertdialog.md | 45 ++ docs/library/chakra/overlay/drawer.md | 70 +++ docs/library/chakra/overlay/menu.md | 31 + docs/library/chakra/overlay/modal.md | 42 ++ docs/library/chakra/overlay/popover.md | 32 + docs/library/chakra/overlay/tooltip.md | 20 + docs/library/chakra/typography/heading.md | 36 ++ docs/library/chakra/typography/highlight.md | 20 + docs/library/chakra/typography/span.md | 21 + docs/library/chakra/typography/text.md | 27 + docs/library/datadisplay/avatar.md | 145 +++++ docs/library/datadisplay/badge.md | 128 ++++ docs/library/datadisplay/callout-ll.md | 140 +++++ docs/library/datadisplay/callout.md | 96 +++ docs/library/datadisplay/codeblock.md | 25 + docs/library/datadisplay/data_editor.md | 356 +++++++++++ docs/library/datadisplay/datatable.md | 67 ++ docs/library/datadisplay/icon.md | 131 ++++ docs/library/datadisplay/list.md | 62 ++ docs/library/datadisplay/progress-ll.md | 55 ++ docs/library/datadisplay/progress.md | 52 ++ docs/library/datadisplay/scroll_area.md | 239 +++++++ docs/library/datadisplay/table.md | 218 +++++++ docs/library/disclosure/accordion.md | 373 +++++++++++ docs/library/disclosure/tabs.md | 323 ++++++++++ docs/library/forms/button.md | 85 +++ docs/library/forms/checkbox.md | 84 +++ docs/library/forms/debounce.md | 38 ++ docs/library/forms/editor.md | 147 +++++ docs/library/forms/form-ll.md | 583 ++++++++++++++++++ docs/library/forms/form.md | 228 +++++++ docs/library/forms/input-ll.md | 123 ++++ docs/library/forms/input.md | 186 ++++++ docs/library/forms/radio_group-ll.md | 504 +++++++++++++++ docs/library/forms/radio_group.md | 128 ++++ docs/library/forms/select-ll.md | 296 +++++++++ docs/library/forms/select.md | 234 +++++++ docs/library/forms/slider.md | 178 ++++++ docs/library/forms/switch.md | 208 +++++++ docs/library/forms/textarea.md | 80 +++ docs/library/forms/upload.md | 254 ++++++++ docs/library/graphing/areachart.md | 247 ++++++++ docs/library/graphing/axis.md | 10 + docs/library/graphing/barchart.md | 245 ++++++++ docs/library/graphing/brush.md | 81 +++ docs/library/graphing/cartesianaxisgrid.md | 157 +++++ docs/library/graphing/composedchart.md | 156 +++++ docs/library/graphing/errorbar.md | 105 ++++ docs/library/graphing/funnelchart.md | 94 +++ docs/library/graphing/label.md | 91 +++ docs/library/graphing/legend.md | 157 +++++ docs/library/graphing/linechart.md | 169 +++++ docs/library/graphing/piechart.md | 188 ++++++ docs/library/graphing/plotly.md | 113 ++++ docs/library/graphing/radarchart.md | 173 ++++++ docs/library/graphing/reference.md | 102 +++ docs/library/graphing/scatterchart.md | 168 +++++ docs/library/graphing/tooltip.md | 156 +++++ docs/library/graphing/treemap.md | 369 +++++++++++ docs/library/layout/aspectratio.md | 105 ++++ docs/library/layout/box.md | 46 ++ docs/library/layout/card.md | 58 ++ docs/library/layout/center.md | 21 + docs/library/layout/cond.md | 80 +++ docs/library/layout/container.md | 40 ++ docs/library/layout/flex.md | 188 ++++++ docs/library/layout/foreach.md | 198 ++++++ docs/library/layout/fragment.md | 22 + docs/library/layout/grid.md | 40 ++ docs/library/layout/inset.md | 90 +++ docs/library/layout/match.md | 289 +++++++++ docs/library/layout/section.md | 36 ++ docs/library/layout/separator.md | 59 ++ docs/library/layout/spacer.md | 25 + docs/library/layout/stack.md | 165 +++++ docs/library/media/audio.md | 22 + docs/library/media/video.md | 22 + docs/library/other/html.md | 38 ++ docs/library/other/script.md | 38 ++ docs/library/overlay/alertdialog.md | 297 +++++++++ docs/library/overlay/contextmenu.md | 223 +++++++ docs/library/overlay/dialog.md | 193 ++++++ docs/library/overlay/drawer.md | 45 ++ docs/library/overlay/dropdownmenu.md | 244 ++++++++ docs/library/overlay/hovercard.md | 125 ++++ docs/library/overlay/popover.md | 146 +++++ docs/library/overlay/tooltip.md | 28 + docs/library/theming/theme.md | 16 + docs/library/theming/theme_panel.md | 18 + docs/library/typography/blockquote.md | 77 +++ docs/library/typography/code.md | 110 ++++ docs/library/typography/em.md | 16 + docs/library/typography/heading.md | 152 +++++ docs/library/typography/kbd.md | 36 ++ docs/library/typography/link.md | 140 +++++ docs/library/typography/markdown.md | 113 ++++ docs/library/typography/quote.md | 19 + docs/library/typography/strong.md | 16 + docs/library/typography/text.md | 194 ++++++ docs/pages/dynamic_routing.md | 132 ++++ docs/pages/metadata.md | 45 ++ docs/pages/routes.md | 178 ++++++ docs/recipes/checkboxes.md | 67 ++ docs/recipes/filtered-table.md | 98 +++ docs/recipes/navbar.md | 155 +++++ docs/recipes/sidebar.md | 160 +++++ docs/state/overview.md | 155 +++++ docs/styling/custom-stylesheets.md | 77 +++ docs/styling/overview.md | 243 ++++++++ docs/styling/responsive.md | 88 +++ docs/styling/theming.md | 90 +++ docs/substates/overview.md | 118 ++++ docs/tutorial/adding-state.md | 207 +++++++ docs/tutorial/final-app.md | 221 +++++++ docs/tutorial/frontend.md | 262 ++++++++ docs/tutorial/intro.md | 18 + docs/tutorial/setup.md | 55 ++ docs/tutorial/tutorial_style.py | 19 + docs/tutorial/tutorial_utils.py | 65 ++ docs/ui/overview.md | 87 +++ docs/utility_methods/other_methods.md | 14 + docs/utility_methods/router_attributes.md | 59 ++ docs/vars/base_vars.md | 128 ++++ docs/vars/computed_vars.md | 87 +++ docs/vars/custom_vars.md | 41 ++ docs/vars/var-operations.md | 447 ++++++++++++++ docs/wrapping-react/example.md | 378 ++++++++++++ docs/wrapping-react/imports.md | 146 +++++ docs/wrapping-react/logic.md | 109 ++++ docs/wrapping-react/overview.md | 163 +++++ 214 files changed, 24077 insertions(+) create mode 100644 docs/__init__.py create mode 100644 docs/api-reference/browser_javascript.md create mode 100644 docs/api-reference/browser_storage.md create mode 100644 docs/api-reference/cli.md create mode 100644 docs/api-reference/event_triggers.md create mode 100644 docs/api-reference/special_events.md create mode 100644 docs/api-routes/overview.md create mode 100644 docs/assets/referencing_assets.md create mode 100644 docs/assets/upload_and_download_files.md create mode 100644 docs/client_storage/overview.md create mode 100644 docs/components/conditional_props.md create mode 100644 docs/components/conditional_rendering.md create mode 100644 docs/components/props.md create mode 100644 docs/components/rendering_iterables.md create mode 100644 docs/components/style_props.md create mode 100644 docs/database/overview.md create mode 100644 docs/database/queries.md create mode 100644 docs/database/relationships.md create mode 100644 docs/database/tables.md create mode 100644 docs/datatable_tutorial/__init__.py create mode 100644 docs/datatable_tutorial/add_interactivity.md create mode 100644 docs/datatable_tutorial/add_styling.md create mode 100644 docs/datatable_tutorial/datatable_tutorial_utils.py create mode 100644 docs/datatable_tutorial/live_stream.md create mode 100644 docs/datatable_tutorial/simple_table.md create mode 100644 docs/events/background_events.md create mode 100644 docs/events/chaining_events.md create mode 100644 docs/events/event_arguments.md create mode 100644 docs/events/events_overview.md create mode 100644 docs/events/page_load_events.md create mode 100644 docs/events/setters.md create mode 100644 docs/events/special_events.md create mode 100644 docs/events/yield_events.md create mode 100644 docs/getting-started/configuration.md create mode 100644 docs/getting-started/installation.md create mode 100644 docs/getting-started/introduction.md create mode 100644 docs/getting-started/project-structure.md create mode 100644 docs/hosting/deploy-quick-start.md create mode 100644 docs/hosting/hosting-cli-commands.md create mode 100644 docs/hosting/self-hosting.md create mode 100644 docs/library/chakra/datadisplay/badge.md create mode 100644 docs/library/chakra/datadisplay/divider.md create mode 100644 docs/library/chakra/datadisplay/list.md create mode 100644 docs/library/chakra/datadisplay/stat.md create mode 100644 docs/library/chakra/datadisplay/table.md create mode 100644 docs/library/chakra/disclosure/accordion.md create mode 100644 docs/library/chakra/disclosure/tabs.md create mode 100644 docs/library/chakra/feedback/alert.md create mode 100644 docs/library/chakra/feedback/circularprogress.md create mode 100644 docs/library/chakra/feedback/progress.md create mode 100644 docs/library/chakra/feedback/skeleton.md create mode 100644 docs/library/chakra/feedback/spinner.md create mode 100644 docs/library/chakra/forms/button.md create mode 100644 docs/library/chakra/forms/buttongroup.md create mode 100644 docs/library/chakra/forms/checkbox.md create mode 100644 docs/library/chakra/forms/editable.md create mode 100644 docs/library/chakra/forms/form.md create mode 100644 docs/library/chakra/forms/formcontrol.md create mode 100644 docs/library/chakra/forms/input.md create mode 100644 docs/library/chakra/forms/numberinput.md create mode 100644 docs/library/chakra/forms/pininput.md create mode 100644 docs/library/chakra/forms/radiogroup.md create mode 100644 docs/library/chakra/forms/rangeslider.md create mode 100644 docs/library/chakra/forms/select.md create mode 100644 docs/library/chakra/forms/slider.md create mode 100644 docs/library/chakra/forms/switch.md create mode 100644 docs/library/chakra/forms/textarea.md create mode 100644 docs/library/chakra/layout/aspectratio.md create mode 100644 docs/library/chakra/layout/box.md create mode 100644 docs/library/chakra/layout/card.md create mode 100644 docs/library/chakra/layout/center.md create mode 100644 docs/library/chakra/layout/container.md create mode 100644 docs/library/chakra/layout/flex.md create mode 100644 docs/library/chakra/layout/grid.md create mode 100644 docs/library/chakra/layout/responsivegrid.md create mode 100644 docs/library/chakra/layout/spacer.md create mode 100644 docs/library/chakra/layout/stack.md create mode 100644 docs/library/chakra/layout/wrap.md create mode 100644 docs/library/chakra/media/avatar.md create mode 100644 docs/library/chakra/media/icon.md create mode 100644 docs/library/chakra/media/image.md create mode 100644 docs/library/chakra/navigation/breadcrumb.md create mode 100644 docs/library/chakra/navigation/link.md create mode 100644 docs/library/chakra/navigation/linkoverlay.md create mode 100644 docs/library/chakra/overlay/alertdialog.md create mode 100644 docs/library/chakra/overlay/drawer.md create mode 100644 docs/library/chakra/overlay/menu.md create mode 100644 docs/library/chakra/overlay/modal.md create mode 100644 docs/library/chakra/overlay/popover.md create mode 100644 docs/library/chakra/overlay/tooltip.md create mode 100644 docs/library/chakra/typography/heading.md create mode 100644 docs/library/chakra/typography/highlight.md create mode 100644 docs/library/chakra/typography/span.md create mode 100644 docs/library/chakra/typography/text.md create mode 100644 docs/library/datadisplay/avatar.md create mode 100644 docs/library/datadisplay/badge.md create mode 100644 docs/library/datadisplay/callout-ll.md create mode 100644 docs/library/datadisplay/callout.md create mode 100644 docs/library/datadisplay/codeblock.md create mode 100644 docs/library/datadisplay/data_editor.md create mode 100644 docs/library/datadisplay/datatable.md create mode 100644 docs/library/datadisplay/icon.md create mode 100644 docs/library/datadisplay/list.md create mode 100644 docs/library/datadisplay/progress-ll.md create mode 100644 docs/library/datadisplay/progress.md create mode 100644 docs/library/datadisplay/scroll_area.md create mode 100644 docs/library/datadisplay/table.md create mode 100644 docs/library/disclosure/accordion.md create mode 100644 docs/library/disclosure/tabs.md create mode 100644 docs/library/forms/button.md create mode 100644 docs/library/forms/checkbox.md create mode 100644 docs/library/forms/debounce.md create mode 100644 docs/library/forms/editor.md create mode 100644 docs/library/forms/form-ll.md create mode 100644 docs/library/forms/form.md create mode 100644 docs/library/forms/input-ll.md create mode 100644 docs/library/forms/input.md create mode 100644 docs/library/forms/radio_group-ll.md create mode 100644 docs/library/forms/radio_group.md create mode 100644 docs/library/forms/select-ll.md create mode 100644 docs/library/forms/select.md create mode 100644 docs/library/forms/slider.md create mode 100644 docs/library/forms/switch.md create mode 100644 docs/library/forms/textarea.md create mode 100644 docs/library/forms/upload.md create mode 100644 docs/library/graphing/areachart.md create mode 100644 docs/library/graphing/axis.md create mode 100644 docs/library/graphing/barchart.md create mode 100644 docs/library/graphing/brush.md create mode 100644 docs/library/graphing/cartesianaxisgrid.md create mode 100644 docs/library/graphing/composedchart.md create mode 100644 docs/library/graphing/errorbar.md create mode 100644 docs/library/graphing/funnelchart.md create mode 100644 docs/library/graphing/label.md create mode 100644 docs/library/graphing/legend.md create mode 100644 docs/library/graphing/linechart.md create mode 100644 docs/library/graphing/piechart.md create mode 100644 docs/library/graphing/plotly.md create mode 100644 docs/library/graphing/radarchart.md create mode 100644 docs/library/graphing/reference.md create mode 100644 docs/library/graphing/scatterchart.md create mode 100644 docs/library/graphing/tooltip.md create mode 100644 docs/library/graphing/treemap.md create mode 100644 docs/library/layout/aspectratio.md create mode 100644 docs/library/layout/box.md create mode 100644 docs/library/layout/card.md create mode 100644 docs/library/layout/center.md create mode 100644 docs/library/layout/cond.md create mode 100644 docs/library/layout/container.md create mode 100644 docs/library/layout/flex.md create mode 100644 docs/library/layout/foreach.md create mode 100644 docs/library/layout/fragment.md create mode 100644 docs/library/layout/grid.md create mode 100644 docs/library/layout/inset.md create mode 100644 docs/library/layout/match.md create mode 100644 docs/library/layout/section.md create mode 100644 docs/library/layout/separator.md create mode 100644 docs/library/layout/spacer.md create mode 100644 docs/library/layout/stack.md create mode 100644 docs/library/media/audio.md create mode 100644 docs/library/media/video.md create mode 100644 docs/library/other/html.md create mode 100644 docs/library/other/script.md create mode 100644 docs/library/overlay/alertdialog.md create mode 100644 docs/library/overlay/contextmenu.md create mode 100644 docs/library/overlay/dialog.md create mode 100644 docs/library/overlay/drawer.md create mode 100644 docs/library/overlay/dropdownmenu.md create mode 100644 docs/library/overlay/hovercard.md create mode 100644 docs/library/overlay/popover.md create mode 100644 docs/library/overlay/tooltip.md create mode 100644 docs/library/theming/theme.md create mode 100644 docs/library/theming/theme_panel.md create mode 100644 docs/library/typography/blockquote.md create mode 100644 docs/library/typography/code.md create mode 100644 docs/library/typography/em.md create mode 100644 docs/library/typography/heading.md create mode 100644 docs/library/typography/kbd.md create mode 100644 docs/library/typography/link.md create mode 100644 docs/library/typography/markdown.md create mode 100644 docs/library/typography/quote.md create mode 100644 docs/library/typography/strong.md create mode 100644 docs/library/typography/text.md create mode 100644 docs/pages/dynamic_routing.md create mode 100644 docs/pages/metadata.md create mode 100644 docs/pages/routes.md create mode 100644 docs/recipes/checkboxes.md create mode 100644 docs/recipes/filtered-table.md create mode 100644 docs/recipes/navbar.md create mode 100644 docs/recipes/sidebar.md create mode 100644 docs/state/overview.md create mode 100644 docs/styling/custom-stylesheets.md create mode 100644 docs/styling/overview.md create mode 100644 docs/styling/responsive.md create mode 100644 docs/styling/theming.md create mode 100644 docs/substates/overview.md create mode 100644 docs/tutorial/adding-state.md create mode 100644 docs/tutorial/final-app.md create mode 100644 docs/tutorial/frontend.md create mode 100644 docs/tutorial/intro.md create mode 100644 docs/tutorial/setup.md create mode 100644 docs/tutorial/tutorial_style.py create mode 100644 docs/tutorial/tutorial_utils.py create mode 100644 docs/ui/overview.md create mode 100644 docs/utility_methods/other_methods.md create mode 100644 docs/utility_methods/router_attributes.md create mode 100644 docs/vars/base_vars.md create mode 100644 docs/vars/computed_vars.md create mode 100644 docs/vars/custom_vars.md create mode 100644 docs/vars/var-operations.md create mode 100644 docs/wrapping-react/example.md create mode 100644 docs/wrapping-react/imports.md create mode 100644 docs/wrapping-react/logic.md create mode 100644 docs/wrapping-react/overview.md diff --git a/docs/__init__.py b/docs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/api-reference/browser_javascript.md b/docs/api-reference/browser_javascript.md new file mode 100644 index 000000000..63f340623 --- /dev/null +++ b/docs/api-reference/browser_javascript.md @@ -0,0 +1,214 @@ +```python exec +import asyncio +from typing import Any +import reflex as rx +from pcweb.pages.docs import wrapping_react +from pcweb.pages.docs import library +``` + +# Browser Javascript + +Reflex compiles your frontend code, defined as python functions, into a Javascript web application +that runs in the user's browser. There are instances where you may need to supply custom javascript +code to interop with Web APIs, use certain third-party libraries, or wrap low-level functionality +that is not exposed via Reflex's Python API. + +```md alert +# Avoid Custom Javascript + +Custom Javascript code in your Reflex app presents a maintenance challenge, as it will be harder to debug and may be unstable across Reflex versions. + +Prefer to use the Python API whenever possible and file an issue if you need additional functionality that is not currently provided. +``` + +## Executing Script + +There are four ways to execute custom Javascript code into your Reflex app: + +* `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library]({library.other.script.path}). + * These components can be directly included in the body of a page, or they may + be passed to `rx.App(head_components=[rx.script(...)])` to be included in + the `` tag of all pages. +* `rx.call_script` - An event handler that evaluates arbitrary Javascript code, + and optionally returns the result to another event handler. + +These previous two methods can work in tandem to load external scripts and then +call functions defined within them in response to user events. + +The following two methods are geared towards wrapping components and are +described with examples in the [Wrapping React]({wrapping_react.overview.path}) +section. + +* `_get_hooks` and `_get_custom_code` in an `rx.Component` subclass +* `Var.create` with `_var_is_local=False` + +## Inline Scripts + +The `rx.script` component is the recommended way to load inline Javascript for greater control over +frontend behavior. + +The functions and variables in the script can be accessed from backend event +handlers or frontend event triggers via the `rx.call_script` interface. + +```python demo exec +class SoundEffectState(rx.State): + @rx.background + async def delayed_play(self): + await asyncio.sleep(1) + return rx.call_script("playFromStart(button_sfx)") + + +def sound_effect_demo(): + return rx.hstack( + rx.script(""" + var button_sfx = new Audio("/vintage-button-sound-effect.mp3") + function playFromStart (sfx) {sfx.load(); sfx.play()}"""), + rx.button("Play Immediately", on_click=rx.call_script("playFromStart(button_sfx)")), + rx.button("Play Later", on_click=SoundEffectState.delayed_play), + ) +``` + +## External Scripts + +External scripts can be loaded either from the `assets` directory, or from CDN URL, and then controlled +via `rx.call_script`. + +```python demo +rx.vstack( + rx.script( + src="https://cdn.jsdelivr.net/gh/scottschiller/snowstorm@snowstorm_20131208/snowstorm-min.js", + on_ready=rx.call_script("snowStorm.autoStart = false; snowStorm.snowColor = '#111'"), + ), + rx.button("Start Duststorm", on_click=rx.call_script("snowStorm.start()")), + rx.button("Toggle Duststorm", on_click=rx.call_script("snowStorm.toggleSnow()")), +) +``` + +## Accessing Client Side Values + +The `rx.call_script` function accepts a `callback` parameter that expects an +Event Handler with one argument which will receive the result of evaluating the +Javascript code. This can be used to access client-side values such as the +`window.location` or current scroll location, or any previously defined value. + +```python demo exec +class WindowState(rx.State): + location: dict[str, str] = {} + scroll_position: dict[str, int] = {} + + def update_location(self, location): + self.location = location + + def update_scroll_position(self, scroll_position): + self.scroll_position = { + "x": scroll_position[0], + "y": scroll_position[1], + } + + def get_client_values(self): + return [ + rx.call_script( + "window.location", + callback=WindowState.update_location + ), + rx.call_script( + "[window.scrollX, window.scrollY]", + callback=WindowState.update_scroll_position, + ), + ] + + +def window_state_demo(): + return rx.vstack( + rx.button("Update Values", on_click=WindowState.get_client_values), + rx.text(f"Scroll Position: {WindowState.scroll_position.to_string()}"), + rx.text("window.location:"), + rx.text_area(value=WindowState.location.to_string(), is_read_only=True), + on_mount=WindowState.get_client_values, + ) +``` + +```md alert +# Allowed Callback Values + +The `callback` parameter may be an `EventHandler` with one argument, or a lambda with one argument that returns an `EventHandler`. +If the callback is None, then no event is triggered. +``` + +## Using React Hooks + +To use React Hooks directly in a Reflex app, you must subclass `rx.Component`, +typically `rx.Fragment` is used when the hook functionality has no visual +element. The hook code is returned by the `_get_hooks` method, which is expected +to return a `str` containing Javascript code which will be inserted into the +page component (i.e the render function itself). + +For supporting code that must be defined outside of the component render +function, use `_get_custom_code`. + +The following example uses `useEffect` to register global hotkeys on the +`document` object, and then triggers an event when a specific key is pressed. + +```python demo exec +class GlobalKeyState(rx.State): + key: str = "" + + def update_key(self, key): + self.key = key + + +class GlobalKeyWatcher(rx.Fragment): + # List of keys to trigger on + keys: rx.vars.Var[list[str]] = [] + + def _get_imports(self) -> rx.utils.imports.ImportDict: + return rx.utils.imports.merge_imports( + super()._get_imports(), + { + "react": {rx.utils.imports.ImportVar(tag="useEffect")} + }, + ) + + def _get_hooks(self) -> str | None: + return """ + useEffect(() => { + const handle_key = (_e0) => { + if (%s.includes(_e0.key)) + %s + } + document.addEventListener("keydown", handle_key, false); + return () => { + document.removeEventListener("keydown", handle_key, false); + } + }) + """ % ( + self.keys, + rx.utils.format.format_event_chain(self.event_triggers["on_key_down"]), + ) + + def get_event_triggers(self) -> dict[str, Any]: + return { + "on_key_down": lambda e0: [e0.key], + } + + def render(self): + return "" # No visual element, hooks only + + +def global_key_demo(): + return rx.vstack( + GlobalKeyWatcher.create( + keys=["a", "s", "d", "w"], + on_key_down=GlobalKeyState.update_key, + ), + rx.text("Press a, s, d or w to trigger an event"), + rx.heading(f"Last watched key pressed: {GlobalKeyState.key}"), + ) +``` + +```md alert +# rx.utils.format.format_event_chain? + +The `format_event_chain` function is used to format an event trigger defined on the component via `get_event_triggers` into a Javascript expression that can be used to actually trigger the event. +The Javascript code should do minimal work, preferring to hand off execution to a user-supplied python `EventHandler` for processing on the backend. +``` diff --git a/docs/api-reference/browser_storage.md b/docs/api-reference/browser_storage.md new file mode 100644 index 000000000..b089048f9 --- /dev/null +++ b/docs/api-reference/browser_storage.md @@ -0,0 +1,191 @@ +# Browser Storage + +## rx.Cookie + +Represents a state Var that is stored as a cookie in the browser. Currently only supports string values. + + Parameters + +- `name` : The name of the cookie on the client side. +- `path`: The cookie path. Use `/` to make the cookie accessible on all pages. +- `max_age` : Relative max age of the cookie in seconds from when the client receives it. +- `domain`: Domain for the cookie (e.g., `sub.domain.com` or `.allsubdomains.com`). +- `secure`: If the cookie is only accessible through HTTPS. +- `same_site`: Whether the cookie is sent with third-party requests. Can be one of (`True`, `False`, `None`, `lax`, `strict`). + +```python +class CookieState(rx.State): + c1: str = rx.Cookie() + c2: str = rx.Cookie('c2 default') + + # cookies with custom settings + c3: str = rx.Cookie(max_age=2) # expires after 2 second + c4: str = rx.Cookie(same_site='strict') + c5: str = rx.Cookie(path='/foo/') # only accessible on `/foo/` + c6: str = rx.Cookie(name='c6-custom-name') +``` + +## rx.remove_cookies + +Remove a cookie from the client's browser. + +Parameters: + +- `key`: The name of cookie to remove. + +```python +rx.button( + 'Remove cookie', on_click=rx.remove_cookie('key') +) +``` + +This event can also be returned from an event handler: + +```python +class CookieState(rx.State): + ... + def logout(self): + return rx.remove_cookie('auth_token') +``` + +## rx.LocalStorage + +Represents a state Var that is stored in localStorage in the browser. Currently only supports string values. + +Parameters + +- `name`: The name of the storage key on the client side. + +```python +class LocalStorageState(rx.State): + # local storage with default settings + l1: str = rx.LocalStorage() + + # local storage with custom settings + l2: str = rx.LocalStorage("l2 default") + l3: str = rx.LocalStorage(name="l3") +``` + +## rx.remove_local_storage + +Remove a local storage item from the client's browser. + +Parameters + +- `key`: The key to remove from local storage. + +```python +rx.button( + 'Remove Local Storage', + on_click=rx.remove_local_storage('key'), +) +``` + +This event can also be returned from an event handler: + +```python +class LocalStorageState(rx.State): + ... + def logout(self): + return rx.remove_local_storage('local_storage_state.l1') +``` + +## rx.clear_local_storage() + +Clear all local storage items from the client's browser. This may affect other +apps running in the same domain or libraries within your app that use local +storage. + +```python +rx.button( + 'Clear all Local Storage', + on_click=rx.clear_local_storage(), +) +``` + +# Serialization Strategies + +If a non-trivial data structure should be stored in a `Cookie` or `LocalStorage` var it needs to +be serialized before and after storing it. It is recommended to use `rx.Base` for the data +which provides simple serialization helpers and works recursively in complex object structures. + +```python demo exec +import reflex as rx + + +class AppSettings(rx.Base): + theme: str = 'light' + sidebar_visible: bool = True + update_frequency: int = 60 + error_messages: list[str] = [] + + +class ComplexLocalStorageState(rx.State): + data_raw: str = rx.LocalStorage("{}") + data: AppSettings = AppSettings() + settings_open: bool = False + + def save_settings(self): + self.data_raw = self.data.json() + self.settings_open = False + + def open_settings(self): + self.data = AppSettings.parse_raw(self.data_raw) + self.settings_open = True + + def set_field(self, field, value): + setattr(self.data, field, value) + + +def app_settings(): + return rx.chakra.form( + rx.foreach( + ComplexLocalStorageState.data.error_messages, + rx.text, + ), + rx.chakra.form_label( + "Theme", + rx.chakra.input( + value=ComplexLocalStorageState.data.theme, + on_change=lambda v: ComplexLocalStorageState.set_field("theme", v), + ), + ), + rx.chakra.form_label( + "Sidebar Visible", + rx.chakra.switch( + is_checked=ComplexLocalStorageState.data.sidebar_visible, + on_change=lambda v: ComplexLocalStorageState.set_field( + "sidebar_visible", + v, + ), + ), + ), + rx.chakra.form_label( + "Update Frequency (seconds)", + rx.chakra.number_input( + value=ComplexLocalStorageState.data.update_frequency, + on_change=lambda v: ComplexLocalStorageState.set_field( + "update_frequency", + v, + ), + ), + ), + rx.button("Save", type="submit"), + on_submit=lambda _: ComplexLocalStorageState.save_settings(), + ) + +def app_settings_example(): + return rx.fragment( + rx.chakra.modal( + rx.chakra.modal_overlay( + rx.chakra.modal_content( + rx.chakra.modal_header("App Settings"), + rx.chakra.modal_body(app_settings()), + ), + ), + is_open=ComplexLocalStorageState.settings_open, + on_close=ComplexLocalStorageState.set_settings_open(False), + ), + rx.button("App Settings", on_click=ComplexLocalStorageState.open_settings), + ) +``` diff --git a/docs/api-reference/cli.md b/docs/api-reference/cli.md new file mode 100644 index 000000000..0d90a62e5 --- /dev/null +++ b/docs/api-reference/cli.md @@ -0,0 +1,90 @@ +# CLI + +The `reflex` command line interface (CLI) is a tool for creating and managing Reflex apps. + +To see a list of all available commands, run `reflex --help`. + +```bash +$ reflex --help + +Usage: reflex [OPTIONS] COMMAND [ARGS]... + + Reflex CLI to create, run, and deploy apps. + +Options: + -v, --version Get the Reflex version. + --help Show this message and exit. + +Commands: + db Subcommands for managing the database schema. + demo Run the demo app. + deploy Deploy the app to the Reflex hosting service. + deployments Subcommands for managing the Deployments. + export Export the app to a zip file. + init Initialize a new Reflex app in the current directory. + login Authenticate with Reflex hosting service. + logout Log out of access to Reflex hosting service. + run Run the app in the current directory. +``` + +## Init + +The `reflex init` command creates a new Reflex app in the current directory. +If an `rxconfig.py` file already exists already, it will re-initialize the app with the latest template. + +```bash +$ reflex init --help +Usage: reflex init [OPTIONS] + + Initialize a new Reflex app in the current directory. + +Options: + --name APP_NAME The name of the app to initialize. + --template [demo|sidebar|blank] + The template to initialize the app with. + --loglevel [debug|info|warning|error|critical] + The log level to use. [default: + LogLevel.INFO] + --help Show this message and exit. +``` + +## Run + +The `reflex run` command runs the app in the current directory. + +By default it runs your app in development mode. +This means that the app will automatically reload when you make changes to the code. +You can also run in production mode which will create an optimized build of your app. + +You can configure the mode, as well as other options through flags. + +```bash +$ reflex run --help +Usage: reflex run [OPTIONS] + + Run the app in the current directory. + +Options: + --env [dev|prod] The environment to run the app in. + [default: Env.DEV] + --frontend-only Execute only frontend. + --backend-only Execute only backend. + --frontend-port TEXT Specify a different frontend port. + [default: 3000] + --backend-port TEXT Specify a different backend port. [default: + 8000] + --backend-host TEXT Specify the backend host. [default: + 0.0.0.0] + --loglevel [debug|info|warning|error|critical] + The log level to use. [default: + LogLevel.INFO] + --help Show this message and exit. +``` + +## Export + +You can export your app's frontend and backend to zip files using the `reflex export` command. + +The frontend is a compiled NextJS app, which can be deployed to a static hosting service like Github Pages or Vercel. +However this is just a static build, so you will need to deploy the backend separately. +See the self-hosting guide for more information. diff --git a/docs/api-reference/event_triggers.md b/docs/api-reference/event_triggers.md new file mode 100644 index 000000000..e1a80e5f4 --- /dev/null +++ b/docs/api-reference/event_triggers.md @@ -0,0 +1,263 @@ +```python exec +from datetime import datetime + +import reflex as rx + +from pcweb.templates.docpage import docdemo, h1_comp, text_comp, docpage + +SYNTHETIC_EVENTS = [ + { + "name": "on_focus", + "description": "The on_focus event handler is called when the element (or some element inside of it) receives focus. For example, it’s called when the user clicks on a text input.", + "state": """class FocusState(rx.State): + text = "Change Me!" + + def change_text(self, text): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.chakra.input(value = FocusState.text, on_focus=FocusState.change_text)""", + }, + { + "name": "on_blur", + "description": "The on_blur event handler is called when focus has left the element (or left some element inside of it). For example, it’s called when the user clicks outside of a focused text input.", + "state": """class BlurState(rx.State): + text = "Change Me!" + + def change_text(self, text): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.chakra.input(value = BlurState.text, on_blur=BlurState.change_text)""", + }, + { + "name": "on_change", + "description": "The on_change event handler is called when the value of an element has changed. For example, it’s called when the user types into a text input each keystoke triggers the on change.", + "state": """class ChangeState(rx.State): + checked: bool = False + +""", + "example": """rx.switch(on_change=ChangeState.set_checked)""", + }, + { + "name": "on_click", + "description": "The on_click event handler is called when the user clicks on an element. For example, it’s called when the user clicks on a button.", + "state": """class ClickState(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(ClickState.text, on_click=ClickState.change_text)""", + }, + { + "name": "on_context_menu", + "description": "The on_context_menu event handler is called when the user right-clicks on an element. For example, it’s called when the user right-clicks on a button.", + "state": """class ContextState(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(ContextState.text, on_context_menu=ContextState.change_text)""", + }, + { + "name": "on_double_click", + "description": "The on_double_click event handler is called when the user double-clicks on an element. For example, it’s called when the user double-clicks on a button.", + "state": """class DoubleClickState(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(DoubleClickState.text, on_double_click=DoubleClickState.change_text)""", + }, + { + "name": "on_mount", + "description": "The on_mount event handler is called after the component is rendered on the page. It is similar to a page on_load event, although it does not necessarily fire when navigating between pages.", + "state": """class MountState(rx.State): + events: list[str] = [] + + def on_mount(self): + self.events = self.events[-4:] + ["on_mount @ " + str(datetime.now())] +""", + "example": """rx.vstack(rx.foreach(MountState.events, rx.text), on_mount=MountState.on_mount)""", + }, + { + "name": "on_unmount", + "description": "The on_unmount event handler is called after removing the component from the page. However, on_unmount will only be called for internal navigation, not when following external links or refreshing the page.", + "state": """class UnmountState(rx.State): + events: list[str] = [] + + def on_unmount(self): + self.events = self.events[-4:] + ["on_unmount @ " + str(datetime.now())] +""", + "example": """rx.vstack(rx.foreach(UnmountState.events, rx.text), on_unmount=UnmountState.on_unmount)""", + }, + { + "name": "on_mouse_up", + "description": "The on_mouse_up event handler is called when the user releases a mouse button on an element. For example, it’s called when the user releases the left mouse button on a button.", + "state": """class MouseUpState(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseUpState.text, on_mouse_up=MouseUpState.change_text)""", + }, + { + "name": "on_mouse_down", + "description": "The on_mouse_down event handler is called when the user presses a mouse button on an element. For example, it’s called when the user presses the left mouse button on a button.", + "state": """class MouseDown(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseDown.text, on_mouse_down=MouseDown.change_text)""", + }, + { + "name": "on_mouse_enter", + "description": "The on_mouse_enter event handler is called when the user’s mouse enters an element. For example, it’s called when the user’s mouse enters a button.", + "state": """class MouseEnter(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseEnter.text, on_mouse_enter=MouseEnter.change_text)""", + }, + { + "name": "on_mouse_leave", + "description": "The on_mouse_leave event handler is called when the user’s mouse leaves an element. For example, it’s called when the user’s mouse leaves a button.", + "state": """class MouseLeave(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseLeave.text, on_mouse_leave=MouseLeave.change_text)""", + }, + { + "name": "on_mouse_move", + "description": "The on_mouse_move event handler is called when the user moves the mouse over an element. For example, it’s called when the user moves the mouse over a button.", + "state": """class MouseMove(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseMove.text, on_mouse_move=MouseMove.change_text)""", + }, + { + "name": "on_mouse_out", + "description": "The on_mouse_out event handler is called when the user’s mouse leaves an element. For example, it’s called when the user’s mouse leaves a button.", + "state": """class MouseOut(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseOut.text, on_mouse_out=MouseOut.change_text)""", + }, + { + "name": "on_mouse_over", + "description": "The on_mouse_over event handler is called when the user’s mouse enters an element. For example, it’s called when the user’s mouse enters a button.", + "state": """class MouseOver(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.button(MouseOver.text, on_mouse_over=MouseOver.change_text)""", + }, + { + "name": "on_scroll", + "description": "The on_scroll event handler is called when the user scrolls the page. For example, it’s called when the user scrolls the page down.", + "state": """class ScrollState(rx.State): + text = "Change Me!" + + def change_text(self): + if self.text == "Change Me!": + self.text = "Changed!" + else: + self.text = "Change Me!" +""", + "example": """rx.vstack( + rx.text("Scroll to make the text below change."), + rx.text(ScrollState.text), + rx.text("Scroll to make the text above change."), + on_scroll=ScrollState.change_text, + overflow = "auto", + height = "3em", + width = "100%", + )""", + }, +] +for i in SYNTHETIC_EVENTS: + exec(i["state"]) + +def component_grid(): + events = [] + for event in SYNTHETIC_EVENTS: + events.append( + rx.vstack( + h1_comp(text=event["name"]), + text_comp(text=event["description"]), + docdemo( + event["example"], state=event["state"], comp=eval(event["example"]) + ), + align_items="left", + ) + ) + + return rx.box(*events) +``` + +# Event Triggers + +Components can modify the state based on user events such as clicking a button or entering text in a field. +These events are triggered by event triggers. + +Event triggers are component specific and are listed in the documentation for each component. + +```python eval +rx.box( + rx.chakra.divider(), + component_grid(), +) +``` diff --git a/docs/api-reference/special_events.md b/docs/api-reference/special_events.md new file mode 100644 index 000000000..5fb907301 --- /dev/null +++ b/docs/api-reference/special_events.md @@ -0,0 +1,93 @@ +```python exec +import reflex as rx +``` + +# Special Events + +Reflex includes a set of built-in special events that can be utilized as event triggers +or returned from event handlers in your applications. These events enhance interactivity and user experience. +Below are the special events available in Reflex, along with explanations of their functionality: + +## rx.console_log + +Perform a console.log in the browser's console. + +```python demo +rx.button('Log', on_click=rx.console_log('Hello World!')) +``` + +When triggered, this event logs a specified message to the browser's developer console. +It's useful for debugging and monitoring the behavior of your application. + +## rx.redirect + +Redirect the user to a new path within the application. + +### Parameters + +- `path`: The destination path or URL to which the user should be redirected. +- `external`: If set to True, the redirection will open in a new tab. Defaults to `False`. + +```python demo +rx.vstack( + rx.button("open in tab", on_click=rx.redirect("/docs/api-reference/special-events")), + rx.button("open in new tab", on_click=rx.redirect('https://github.com/reflex-dev/reflex/', external=True)) +) +``` + +When this event is triggered, it navigates the user to a different page or location within your Reflex application. +By default, the redirection occurs in the same tab. However, if you set the external parameter to True, the redirection +will open in a new tab or window, providing a seamless user experience. + +## rx.set_clipboard + +Set the specified text content to the clipboard. + +```python demo +rx.button('Copy "Hello World" to clipboard',on_click=rx.set_clipboard('Hello World'),) +``` + +This event allows you to copy a given text or content to the user's clipboard. +It's handy when you want to provide a "Copy to Clipboard" feature in your application, +allowing users to easily copy information to paste elsewhere. + +## rx.set_value + +Set the value of a specified reference element. + +```python demo +rx.hstack( + rx.chakra.input(id='input1'), + rx.button( + 'Erase', on_click=rx.set_value('input1', '') + ), +) +``` + +With this event, you can modify the value of a particular HTML element, typically an input field or another form element. + +## rx.window_alert + +Create a window alert in the browser. + +```python demo +rx.button('Alert', on_click=rx.window_alert('Hello World!')) +``` + +## rx.download + +Download a file at a given path. + +Parameters: + +- `url`: The URL of the file to be downloaded. +- `data`: The data to be downloaded. Should be `str` or `bytes`, `data:` URI, `PIL.Image`, or any state Var (to be converted to JSON). +- `filename`: The desired filename of the downloaded file. + +```md alert +`url` and `data` args are mutually exclusive, and at least one of them must be provided. +``` + +```python demo +rx.button("Download", on_click=rx.download(url="/reflex_banner.png", filename="different_name_logo.png")) +``` diff --git a/docs/api-routes/overview.md b/docs/api-routes/overview.md new file mode 100644 index 000000000..24834ea9c --- /dev/null +++ b/docs/api-routes/overview.md @@ -0,0 +1,43 @@ +```python exec +import reflex as rx +``` + +# Backend API Routes + +In addition to your frontend app, Reflex also uses a FastAPI backend to serve your app. + +To add additional endpoints to the backend API, you can use `app.add_api_route` and add a route that returns JSON. + +```python +async def api_test(item_id: int): + return \{"my_result": item_id} + +app = rx.App() +app.api.add_api_route("/items/\{item_id}", api_test) +``` + +Now you can access the endpoint at `localhost:8000/items/23` and get the result. + +This is useful for creating a backend API that can be used for purposes other than your Reflex app. + +## Reserved Routes + +Some routes on the backend are reserved for the runtime of Reflex, and should not be overriden unless you know what you are doing. + +## Ping + +`localhost:8000/ping/`: You can use this route to check the health of the backend. + +The expected return is `"pong"`. + +## Event + +`localhost:8000/_event`: the frontend will use this route to notify the backend that an event occurred. + +```md alert error +# Overriding this route will break the event communication +``` + +## Upload + +`localhost:8000/_upload`: This route is used for the upload of file when using `rx.upload()`. diff --git a/docs/assets/referencing_assets.md b/docs/assets/referencing_assets.md new file mode 100644 index 000000000..81bd1d894 --- /dev/null +++ b/docs/assets/referencing_assets.md @@ -0,0 +1,42 @@ +```python exec +import reflex as rx +``` + +# Assets + +Static files such as images and stylesheets can be placed in `"assets/"` folder of the project. These files can be referenced within your app. + +```md alert +# Assets are copied during the build process. + +Any files placed within the `assets/` folder at runtime will not be available to the app +when running in production mode. The `assets/` folder should only be used for static files. +``` + +## Referencing Assets + +To reference an image in the `"assets/"` simply pass the relative path as a prop. + +For example, you can store your logo in your assets folder: + +```bash +assets +└── Reflex.svg +``` + +Then you can display it using a `rx.image` component: + +```python demo +rx.image(src="/Reflex.svg", width="5em") +``` + +```md alert +Always prefix the asset path with a forward slash `/` to reference the asset from the root of the project, +or it may not display correctly on non-root pages. +``` + +## Favicon + +The favicon is the small icon that appears in the browser tab. + +You can add a `"favicon.ico"` file to the `"assets/"` folder to change the favicon. diff --git a/docs/assets/upload_and_download_files.md b/docs/assets/upload_and_download_files.md new file mode 100644 index 000000000..64018abdc --- /dev/null +++ b/docs/assets/upload_and_download_files.md @@ -0,0 +1,89 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import library +from pcweb.pages.docs import api_reference +``` + +# Files + +In addition to any assets you ship with your app, many web app will often need to receive or send files, whether you want to share medias, allow user to import their data, or export some backend data. + +In this section, we will cover all you need to know for manipulating files in Reflex. + +## Download + +If you want to let the users of your app download files from your server to their computer, Reflex offer you two way. + +### With a regular link + +For some basic usage, simply providing the path to your resource in a `rx.link` will work, and clicking the link will download or display the resource. + +```python demo +rx.link("Download", href="/reflex_banner.png") +``` + +### With `rx.download` event + +Using the `rx.download` event will always prompt the browser to download the file, even if it could be displayed in the browser. + +The `rx.download` event also allows the download to be triggered from another backend event handler. + +```python demo +rx.button( + "Download", + on_click=rx.download(url="/reflex_banner.png"), +) +``` + +`rx.download` lets you specify a name for the file that will be downloaded, if you want it to be different from the name on the server side. + +```python demo +rx.button( + "Download and Rename", + on_click=rx.download( + url="/reflex_banner.png", + filename="different_name_logo.png" + ), +) +``` + +If the data to download is not already available at a known URL, pass the `data` directly to the `rx.download` event from the backend. + +```python demo exec +import random + +class DownloadState(rx.State): + def download_random_data(self): + return rx.download( + data=",".join([str(random.randint(0, 100)) for _ in range(10)]), + filename="random_numbers.csv" + ) + +def download_random_data_button(): + return rx.button( + "Download random numbers", + on_click=DownloadState.download_random_data + ) +``` + +The `data` arg accepts `str` or `bytes` data, a `data:` URI, `PIL.Image`, or any state Var. If the Var is not already a string, it will be converted to a string using `JSON.stringify`. This allows complex state structures to be offered as JSON downloads. + +Reference page for `rx.download` [here]({api_reference.special_events.path}#rx.download). + +## Upload + +Uploading files to your server let your users interact with your app in a different way than just filling forms to provide data. + +The component `rx.upload` let your users upload files on the server. + +Here is a basic example of how it is used: + +```python +def index(): + return rx.fragment( + rx.upload(rx.text("Upload files"), rx.icon(tag="upload")), + rx.button(on_submit=State.) + ) +``` + +For detailed information, see the reference page of the component [here]({library.forms.upload.path}). diff --git a/docs/client_storage/overview.md b/docs/client_storage/overview.md new file mode 100644 index 000000000..13ffeb2f7 --- /dev/null +++ b/docs/client_storage/overview.md @@ -0,0 +1,34 @@ +```python exec +import reflex as rx +``` + +# Client-storage + +You can use the browser's local storage to persist state between sessions. +This allows user preferences, authentication cookies, other bits of information +to be stored on the client and accessed from different browser tabs. + +A client-side storage var looks and acts like a normal `str` var, except the +default value is either `rx.Cookie` or `rx.LocalStorage` depending on where the +value should be stored. The key name will be based on the var name, but this +can be overridden by passing `name="my_custom_name"` as a keyword argument. + +For more information see [Browser Storage](/docs/api-reference/browser/). + +Try entering some values in the text boxes below and then load the page in a separate +tab or check the storage section of browser devtools to see the values saved in the browser. + +```python demo exec +class ClientStorageState(rx.State): + my_cookie: str = rx.Cookie("") + my_local_storage: str = rx.LocalStorage("") + custom_cookie: str = rx.Cookie(name="CustomNamedCookie", max_age=3600) + + +def client_storage_example(): + return rx.vstack( + rx.hstack(rx.text("my_cookie"), rx.chakra.input(value=ClientStorageState.my_cookie, on_change=ClientStorageState.set_my_cookie)), + rx.hstack(rx.text("my_local_storage"), rx.chakra.input(value=ClientStorageState.my_local_storage, on_change=ClientStorageState.set_my_local_storage)), + rx.hstack(rx.text("custom_cookie"), rx.chakra.input(value=ClientStorageState.custom_cookie, on_change=ClientStorageState.set_custom_cookie)), + ) +``` diff --git a/docs/components/conditional_props.md b/docs/components/conditional_props.md new file mode 100644 index 000000000..b1cd88a6f --- /dev/null +++ b/docs/components/conditional_props.md @@ -0,0 +1,25 @@ +```python exec +import reflex as rx +``` + +# Conditional Props + +Sometimes you want to set a prop based on a condition. You can use the `rx.cond` function to do this. + +```python demo exec +class PropCondState(rx.State): + + value: list[int] + + def set_end(self, value: int): + self.value = value + + +def cond_prop(): + return rx.slider( + default_value=[50], + on_value_commit=PropCondState.set_end, + color_scheme=rx.cond(PropCondState.value[0] > 50, "green", "pink"), + width="100%", + ) +``` diff --git a/docs/components/conditional_rendering.md b/docs/components/conditional_rendering.md new file mode 100644 index 000000000..e5f0cc9df --- /dev/null +++ b/docs/components/conditional_rendering.md @@ -0,0 +1,269 @@ +```python exec +import reflex as rx + +from pcweb.pages.docs import vars, library +``` + +# Conditional Rendering + +We use the `cond` component to conditionally render components. The `cond` component acts in a similar way to a conditional (ternary) operator in python, acting in a similar fashion to an `if-else` statement. + +```md alert +Check out the API reference for [cond docs]({library.layout.cond.path}). +``` + +```python eval +rx.box(height="2em") +``` + +Here is a simple example to show how by checking the value of the state var `show` we can render either `blue` text or `red` text. + +The first argument to the `cond` component is the condition we are checking. Here the condition is the value of the state var boolean `show`. + +If `show` is `True` then the 2nd argument to the `cond` component is rendered, in this case that is `rx.text("Text 1", color="blue")`. + +If `show` is `False` then the 3rd argument to the `cond` component is rendered, in this case that is `rx.text("Text 2", color="red")`. + +```python demo exec +class CondSimpleState(rx.State): + show: bool = True + + def change(self): + self.show = not (self.show) + + +def cond_simple_example(): + return rx.vstack( + rx.button("Toggle", on_click=CondSimpleState.change), + rx.cond( + CondSimpleState.show, + rx.text("Text 1", color="blue"), + rx.text("Text 2", color="red"), + ), + ) +``` + +## Var Operations (negation) + +You can use var operations with the `cond` component. To learn more generally about var operators check out [these docs]({vars.var_operations.path}). The logical operator `~` can be used to negate a condition. In this example we show that by negating the condition `~CondNegativeState.show` within the cond, we then render the `rx.text("Text 1", color="blue")` component when the state var `show` is negative. + +```python demo exec +class CondNegativeState(rx.State): + show: bool = True + + def change(self): + self.show = not (self.show) + + +def cond_negative_example(): + return rx.vstack( + rx.text(f"Value of state var show: {CondNegativeState.show}"), + rx.button("Toggle", on_click=CondNegativeState.change), + rx.cond( + CondNegativeState.show, + rx.text("Text 1", color="blue"), + rx.text("Text 2", color="red"), + ), + rx.cond( + ~CondNegativeState.show, + rx.text("Text 1", color="blue"), + rx.text("Text 2", color="red"), + ), + ) +``` + +## Multiple Conditions + +It is also possible to make up complex conditions using the `logical or` (|) and `logical and` (&) operators. + +Here we have an example using the var operators `>=`, `<=`, `&`. We define a condition that if a person has an age between 18 and 65, including those ages, they are able to work, otherwise they cannot. + +We could equally use the operator `|` to represent a `logical or` in one of our conditions. + +```python demo exec +import random + +class CondComplexState(rx.State): + age: int = 19 + + def change(self): + self.age = random.randint(0, 100) + + +def cond_complex_example(): + return rx.vstack( + rx.button("Toggle", on_click=CondComplexState.change), + rx.text(f"Age: {CondComplexState.age}"), + rx.cond( + (CondComplexState.age >= 18) & (CondComplexState.age <=65), + rx.text("You can work!", color="green"), + rx.text("You cannot work!", color="red"), + ), + ) + +``` + +## Reusing Cond + +We can also reuse a `cond` component several times by defining it within a function that returns a `cond`. + +In this example we define the function `render_item`. This function takes in an `item`, uses the `cond` to check if the item `is_packed`. If it is packed it returns the `item_name` with a `✔` next to it, and if not then it just returns the `item_name`. + +```python demo exec +class ToDoListItem(rx.Base): + item_name: str + is_packed: bool + +class CondRepeatState(rx.State): + to_do_list: list[ToDoListItem] = [ + ToDoListItem(item_name="Space suit", is_packed=True), + ToDoListItem(item_name="Helmet", is_packed=True), + ToDoListItem(item_name="Back Pack", is_packed=False), + ] + + +def render_item(item: [str, bool]): + return rx.cond( + item.is_packed, + rx.chakra.list_item(item.item_name + ' ✔'), + rx.chakra.list_item(item.item_name), + ) + +def packing_list(): + return rx.vstack( + rx.text("Sammy's Packing List"), + rx.chakra.list(rx.foreach(CondRepeatState.to_do_list, render_item)), + ) + +``` + +## Nested Conditional + +We can also nest `cond` components within each other to create more complex logic. In python we can have an `if` statement that then has several `elif` statements before finishing with an `else`. This is also possible in reflex using nested `cond` components. In this example we check whether a number is positive, negative or zero. + +Here is the python logic using `if` statements: + +```python +number = 0 + +if number > 0: + print("Positive number") + +elif number == 0: + print('Zero') +else: + print('Negative number') +``` + +This reflex code that is logically identical: + +```python demo exec +import random + + +class NestedState(rx.State): + + num: int = 0 + + def change(self): + self.num = random.randint(-10, 10) + + +def cond_nested_example(): + return rx.vstack( + rx.button("Toggle", on_click=NestedState.change), + rx.cond( + NestedState.num > 0, + rx.text(f"{NestedState.num} is Positive!", color="orange"), + rx.cond( + NestedState.num == 0, + rx.text(f"{NestedState.num} is Zero!", color="blue"), + rx.text(f"{NestedState.num} is Negative!", color="red"), + ) + ), + ) + +``` + +Here is a more advanced example where we have three numbers and we are checking which of the three is the largest. If any two of them are equal then we return that `Some of the numbers are equal!`. + +The reflex code that follows is logically identical to doing the following in python: + +```python +a = 8 +b = 10 +c = 2 + +if((a>b and a>c) and (a != b and a != c)): + print(a, " is the largest!") +elif((b>a and b>c) and (b != a and b != c)): + print(b, " is the largest!") +elif((c>a and c>b) and (c != a and c != b)): + print(c, " is the largest!") +else: + print("Some of the numbers are equal!") +``` + +```python demo exec +import random + + +class CNS(rx.State): + # CNS: CondNestedState + a: int = 8 + b: int = 10 + c: int = 2 + + + def change(self): + self.a = random.randint(0, 10) + self.b = random.randint(0, 10) + self.c = random.randint(0, 10) + + +def cond_nested_example_2(): + return rx.vstack( + rx.button("Toggle", on_click=CNS.change), + rx.text(f"a: {CNS.a}, b: {CNS.b}, c: {CNS.c}"), + rx.cond( + ((CNS.a > CNS.b) & (CNS.a > CNS.c)) & ((CNS.a != CNS.b) & (CNS.a != CNS.c)), + rx.text(f"{CNS.a} is the largest!", color="green"), + rx.cond( + ((CNS.b > CNS.a) & (CNS.b > CNS.c)) & ((CNS.b != CNS.a) & (CNS.b != CNS.c)), + rx.text(f"{CNS.b} is the largest!", color="orange"), + rx.cond( + ((CNS.c > CNS.a) & (CNS.c > CNS.b)) & ((CNS.c != CNS.a) & (CNS.c != CNS.b)), + rx.text(f"{CNS.c} is the largest!", color="blue"), + rx.text("Some of the numbers are equal!", color="red"), + ), + ), + ), + ) + +``` + +## Cond used as a style prop + +`Cond` can also be used to show and hide content in your reflex app. In this example, we have no third argument to the `cond` operator which means that nothing is rendered if the condition is false. + +```python demo exec +class CondStyleState(rx.State): + show: bool = False + img_url: str = "/preview.png" + def change(self): + self.show = not (self.show) + + +def cond_style_example(): + return rx.vstack( + rx.button("Toggle", on_click=CondStyleState.change), + rx.cond( + CondStyleState.show, + rx.image( + src=CondStyleState.img_url, + height="25em", + width="25em", + ), + ), + ) +``` diff --git a/docs/components/props.md b/docs/components/props.md new file mode 100644 index 000000000..f24f1ed82 --- /dev/null +++ b/docs/components/props.md @@ -0,0 +1,81 @@ +```python exec +from pcweb.pages.docs.library import library +from pcweb.pages.docs import state, vars +import reflex as rx +``` + +# Props + +Props modify the behavior and appearance of a component. They are passed in as keyword arguments to the component function. + +## Component Props + +Each component has props that are specific to that component. For example, the `rx.avatar` component has a fallback prop that sets the `fallback` of the avatar. + +```python demo +rx.avatar( + fallback="JD" +) +``` + +Check the docs for the component you are using to see what props are available. + +```md alert success +# Reflex has a wide selection of [built-in components]({library.path}) to get you started quickly. +``` + +## HTML Props + +Components support many standard HTML properties as props. For example: the HTML [id]({"https://www.w3schools.com/html/html_id.asp"}) property is exposed directly as the prop `id`. The HTML [className]({"https://www.w3schools.com/jsref/prop_html_classname.asp"}) property is exposed as the prop `class_name` (note the Pythonic snake_casing!). + +```python demo +rx.box( + "Hello World", + id="box-id", + class_name=["class-name-1", "class-name-2",], +) +``` + +## Binding Props to State + +Reflex apps can have a [State]({state.overview.path}) that stores all variables that can change when the app is running, as well as the event handlers that can change those variables. + +State may be modified in response to things like user input like clicking a button, or in response to events like loading a page. + +State vars can be bound to component props, so that the UI always reflects the current state of the app. + +```md alert warning +Optional: Learn all about [State]({state.overview.path}) first. +``` + +You can set the value of a prop to a [state var]({vars.base_vars.path}) to make the component update when the var changes. + +Try clicking the badge below to change its color. + +```python demo exec +class PropExampleState(rx.State): + text: str = "Hello World" + color: str = "red" + + def flip_color(self): + if self.color == "red": + self.color = "blue" + else: + self.color = "red" + + +def index(): + return rx.badge( + PropExampleState.text, + color_scheme=PropExampleState.color, + on_click=PropExampleState.flip_color, + font_size="1.5em", + _hover={ + "cursor": "pointer", + } + ) +``` + +In this example, the `color_scheme` prop is bound to the `color` state var. + +When the `flip_color` event handler is called, the `color` var is updated, and the `color_scheme` prop is updated to match. diff --git a/docs/components/rendering_iterables.md b/docs/components/rendering_iterables.md new file mode 100644 index 000000000..619c4471c --- /dev/null +++ b/docs/components/rendering_iterables.md @@ -0,0 +1,197 @@ +```python exec +import reflex as rx + +from pcweb.pages.docs import vars +``` + +# Rendering Iterables + +You will often want to display multiple similar components from a collection of data. The `rx.foreach` component takes an `iterable` (list, tuple or dict) and a `function` that renders each item in the list. This is useful for dynamically rendering a list of items defined in a state. + +In this first simple example we iterate through a `list` of colors and render the name of the color and use this color as the background for that `rx.box`. As we can see we have a function `colored_box` that we pass to the `rx.foreach` component. This function renders each item from the `list` that we have defined as a state var `color`. + +```python demo exec +class IterState(rx.State): + color: list[str] = [ + "red", + "green", + "blue", + "yellow", + "orange", + "purple", + ] + + +def colored_box(color: str): + return rx.box(rx.text(color), background_color=color) + + +def simple_foreach(): + return rx.chakra.responsive_grid( + rx.foreach(IterState.color, colored_box), + columns=[2, 4, 6], + ) + +``` + +```md alert warning +# The type signature of the functions does not matter to the `foreach` component. It's the type annotation on the `state var` that determines what operations are available (e.g. when nesting). +``` + +## Enumeration + +The function can also take an index as a second argument, meaning that we can enumerate through data as shown in the example below. + +```python demo exec +class IterIndexState(rx.State): + color: list[str] = [ + "red", + "green", + "blue", + "yellow", + "orange", + "purple", + ] + + +def enumerate_foreach(): + return rx.chakra.responsive_grid( + rx.foreach( + IterIndexState.color, + lambda color, index: rx.box(rx.text(index), bg=color) + ), + columns=[2, 4, 6], + ) + +``` + +## Dictionary + +We can iterate through a `dict` data structure using a `foreach`. When the dict is passed through to the function that renders each item, it is presented as a list of key-value pairs `[("sky", "blue"), ("balloon", "red"), ("grass", "green")]`. + +```python demo exec +class SimpleDictIterState(rx.State): + color_chart: dict[str, str] = { + "sky": "blue", + "balloon": "red", + "grass": "green", + } + + +def display_color(color: list): + # color is presented as a list key-value pairs [("sky", "blue"), ("balloon", "red"), ("grass", "green")] + return rx.box(rx.text(color[0]), bg=color[1], padding_x="1.5em") + + +def dict_foreach(): + return rx.chakra.responsive_grid( + rx.foreach( + SimpleDictIterState.color_chart, + display_color, + ), + columns=[2, 4, 6], + ) + +``` + +## Nested examples + +`rx.foreach` can be used with nested state vars. Here we use nested `foreach` components to render the nested state vars. The `rx.foreach(project["technologies"], get_badge)` inside of the `project_item` function, renders the `dict` values which are of type `list`. The `rx.box(rx.foreach(NestedStateFE.projects, project_item))` inside of the `projects_example` function renders each `dict` inside of the overall state var `projects`. + +```python demo exec +class NestedStateFE(rx.State): + projects: list[dict[str, list]] = [ + { + "technologies": ["Next.js", "Prisma", "Tailwind", "Google Cloud", "Docker", "MySQL"] + }, + { + "technologies": ["Python", "Flask", "Google Cloud", "Docker"] + } + ] + +def get_badge(technology: str) -> rx.Component: + return rx.chakra.badge(technology, variant="subtle", color_scheme="green") + +def project_item(project: dict) -> rx.Component: + return rx.box( + rx.hstack( + rx.foreach(project["technologies"], get_badge) + ), + ) + +def projects_example() -> rx.Component: + return rx.box(rx.foreach(NestedStateFE.projects, project_item)) +``` + +If you want an example where not all of the values in the dict are the same type then check out the example on [var operations using foreach]({vars.var_operations.path}). + +Here is a further example of how to use `foreach` with a nested data structure. + +```python demo exec +class NestedDictIterState(rx.State): + color_chart: dict[str, list[str]] = { + "purple": ["red", "blue"], + "orange": ["yellow", "red"], + "green": ["blue", "yellow"], + } + + +def display_colors(color: list[str, list[str]]): + + return rx.vstack( + rx.text(color[0], color=color[0]), + rx.hstack( + rx.foreach( + color[1], + lambda x: rx.box( + rx.text(x, color="black"), bg=x + ), + ) + ), + ) + + +def nested_dict_foreach(): + return rx.chakra.responsive_grid( + rx.foreach( + NestedDictIterState.color_chart, + display_colors, + ), + columns=[2, 4, 6], + ) + +``` + +## Foreach with Cond + +We can also use `foreach` with the `cond` component. + +In this example we define the function `render_item`. This function takes in an `item`, uses the `cond` to check if the item `is_packed`. If it is packed it returns the `item_name` with a `✔` next to it, and if not then it just returns the `item_name`. We use the `foreach` to iterate over all of the items in the `to_do_list` using the `render_item` function. + +```python demo exec +class ToDoListItem(rx.Base): + item_name: str + is_packed: bool + +class ForeachCondState(rx.State): + to_do_list: list[ToDoListItem] = [ + ToDoListItem(item_name="Space suit", is_packed=True), + ToDoListItem(item_name="Helmet", is_packed=True), + ToDoListItem(item_name="Back Pack", is_packed=False), + ] + + +def render_item(item: [str, bool]): + return rx.cond( + item.is_packed, + rx.chakra.list_item(item.item_name + ' ✔'), + rx.chakra.list_item(item.item_name), + ) + +def packing_list(): + return rx.vstack( + rx.text("Sammy's Packing List"), + rx.chakra.list(rx.foreach(ForeachCondState.to_do_list, render_item)), + ) + +``` diff --git a/docs/components/style_props.md b/docs/components/style_props.md new file mode 100644 index 000000000..c5bd955ef --- /dev/null +++ b/docs/components/style_props.md @@ -0,0 +1,25 @@ +```python exec +from pcweb.pages.docs import styling +import reflex as rx +``` + +# Style Props + +In addition to component-specific props, most built-in components support a full range of style props. You can use any CSS property to style a component. + +```python demo +rx.button( + "Fancy Button", + border_radius="1em", + box_shadow="rgba(151, 65, 252, 0.8) 0 15px 30px -10px", + background_image="linear-gradient(144deg,#AF40FF,#5B42F3 50%,#00DDEB)", + box_sizing="border-box", + color="white", + opacity= 1, + _hover={ + "opacity": .5, + } +) +``` + +See the [styling docs]({styling.overview.path}) to learn more about customizing the appearance of your app. diff --git a/docs/database/overview.md b/docs/database/overview.md new file mode 100644 index 000000000..ee0c35257 --- /dev/null +++ b/docs/database/overview.md @@ -0,0 +1,75 @@ +# Database + +Reflex uses [sqlmodel](https://sqlmodel.tiangolo.com) to provide a built-in ORM wrapping SQLAlchemy. + +The examples on this page refer specifically to how Reflex uses various tools to +expose an integrated database interface. Only basic use cases will be covered +below, but you can refer to the +[sqlmodel tutorial](https://sqlmodel.tiangolo.com/tutorial/select/) +for more examples and information, just replace `SQLModel` with `rx.Model` and +`Session(engine)` with `rx.session()` + +For advanced use cases, please see the +[SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/orm/quickstart.html) (v1.4). + +## Connecting + +Reflex provides a built-in SQLite database for storing and retrieving data. + +You can connect to your own SQL compatible database by modifying the +`rxconfig.py` file with your database url. + +```python +config = rx.Config( + app_name="my_app", + db_url="sqlite:///reflex.db", +) +``` + +For more examples of database URLs that can be used, see the [SQLAlchemy +docs](https://docs.sqlalchemy.org/en/14/core/engines.html#backend-specific-urls). +Be sure to install the appropriate DBAPI driver for the database you intend to +use. + +## Tables + +To create a table make a class that inherits from `rx.Model` with and specify +that it is a table. + +```python +class User(rx.Model, table=True): + username: str + email: str + password: str +``` + +## Migrations + +Reflex leverages [alembic](https://alembic.sqlalchemy.org/en/latest/) +to manage database schema changes. + +Before the database feature can be used in a new app you must call `reflex db init` +to initialize alembic and create a migration script with the current schema. + +After making changes to the schema, use +`reflex db makemigrations --message 'something changed'` +to generate a script in the `alembic/versions` directory that will update the +database schema. It is recommended that scripts be inspected before applying +them. + +The `reflex db migrate` command is used to apply migration scripts to bring the +database up to date. During app startup, if Reflex detects that the current +database schema is not up to date, a warning will be displayed on the console. + +## Queries + +To query the database you can create a `rx.session()` +which handles opening and closing the database connection. + +You can use normal SQLAlchemy queries to query the database. + +```python +with rx.session() as session: + session.add(User(username="test", email="admin@pynecone.io", password="admin")) + session.commit() +``` diff --git a/docs/database/queries.md b/docs/database/queries.md new file mode 100644 index 000000000..199961be7 --- /dev/null +++ b/docs/database/queries.md @@ -0,0 +1,186 @@ +# Queries + +Queries are used to retrieve data from a database. + +A query is a request for information from a database table or combination of +tables. A query can be used to retrieve data from a single table or multiple +tables. A query can also be used to insert, update, or delete data from a table. + +## Session + +To execute a query you must first create a `rx.session`. You can use the session +to query the database using SQLModel or SQLAlchemy syntax. + +The `rx.session` statement will automatically close the session when the code +block is finished. **If `session.commit()` is not called, the changes will be +rolled back and not persisted to the database.** The code can also explicitly +rollback without closing the session via `session.rollback()`. + +The following example shows how to create a session and query the database. +First we create a table called `User`. + +```python +class User(rx.Model, table=True): + username: str + email: str +``` + +### Select + +Then we create a session and query the User table. + +```python +class QueryUser(rx.State): + name: str + users: list[User] + + def get_users(self): + with rx.session() as session: + self.users = session.exec( + User.select.where( + User.username.contains(self.name)).all()) +``` + +The `get_users` method will query the database for all users that contain the +value of the state var `name`. + +On older python versions, the `.select` attribute on model objects does not work, but +you may use `sqlmodel.select(User)` instead. + +### Insert + +Similarly, the `session.add()` method to add a new record to the +database or persist an existing object. + +```python +class AddUser(rx.State): + username: str + email: str + + def add_user(self): + with rx.session() as session: + session.add(User(username=self.username, email=self.email)) + session.commit() +``` + +### Update + +To update the user, first query the database for the object, make the desired +modifications, `.add` the object to the session and finally call `.commit()`. + +```python +class ChangeEmail(rx.State): + username: str + email: str + + def modify_user(self): + with rx.session() as session: + user = session.exec(User.select.where( + (User.username == self.username).first())) + user.email = self.email + session.add(user) + session.commit() +``` + +### Delete + +To delete a user, first query the database for the object, then call +`.delete()` on the session and finally call `.commit()`. + +```python +class RemoveUser(rx.State): + username: str + + def delete_user(self): + with rx.session() as session: + user = session.exec(User.select.where( + User.username == self.username).first()) + session.delete(user) + session.commit() +``` + +## ORM Object Lifecycle + +The objects returned by queries are bound to the session that created them, and cannot generally +be used outside that session. After adding or updating an object, not all fields are automatically +updated, so accessing certain attributes may trigger additional queries to refresh the object. + +To avoid this, the `session.refresh()` method can be used to update the object explicitly and +ensure all fields are up to date before exiting the session. + +```python +class AddUserForm(rx.State): + user: User | None = None + + def add_user(self, form_data: dict[str, str]): + with rx.session() as session: + self.user = User(**form_data) + session.add(self.user) + session.commit() + session.refresh(self.user) +``` + +Now the `self.user` object will have a correct reference to the autogenerated +primary key, `id`, even though this was not provided when the object was created +from the form data. + +If `self.user` needs to be modified or used in another query in a new session, +it must be added to the session. Adding an object to a session does not +necessarily create the object, but rather associates it with a session where it +may either be created or updated accordingly. + +```python +class AddUserForm(rx.State): + ... + + def update_user(self, form_data: dict[str, str]): + if self.user is None: + return + with rx.session() as session: + self.user.set(**form_data) + session.add(self.user) + session.commit() + session.refresh(self.user) +``` + +If an ORM object will be referenced and accessed outside of a session, you +should call `.refresh()` on it to avoid stale object exceptions. + +## Using SQL Directly + +Avoiding SQL is one of the main benefits of using an ORM, but sometimes it is +necessary for particularly complex queries, or when using database-specific +features. + +SQLModel exposes the `session.execute()` method that can be used to execute raw +SQL strings. If parameter binding is needed, the query may be wrapped in +[`sqlalchemy.text`](https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.text), +which allows colon-prefix names to be used as placeholders. + +```md alert +Never use string formatting to construct SQL queries, as this may lead to SQL injection vulnerabilities in the app. +``` + +```python +import sqlalchemy + +import reflex as rx + + +class State(rx.State): + def insert_user_raw(self, username, email): + with rx.session() as session: + session.execute( + sqlalchemy.text( + "INSERT INTO user (username, email) " + "VALUES (:username, :email)" + ), + \{"username": username, "email": email}, + ) + session.commit() + + @rx.var + def raw_user_tuples(self) -> list[list]: + with rx.session() as session: + return [list(row) for row in session.execute("SELECT * FROM user").all()] +``` diff --git a/docs/database/relationships.md b/docs/database/relationships.md new file mode 100644 index 000000000..3bee1d239 --- /dev/null +++ b/docs/database/relationships.md @@ -0,0 +1,162 @@ +# Relationships + +Foreign key relationships are used to link two tables together. For example, +the `Post` model may have a field, `user_id`, with a foreign key of `user.id`, +referencing a `User` model. This would allow us to automatically query the `Post` objects +associated with a user, or find the `User` object associated with a `Post`. + +To establish bidirectional relationships a model must correctly set the +`back_populates` keyword argument on the `Relationship` to the relationship +attribute in the _other_ model. + +## Foreign Key Relationships + +To create a relationship, first add a field to the model that references the +primary key of the related table, then add a `sqlmodel.Relationship` attribute +which can be used to access the related objects. + +Defining relationships like this requires the use of `sqlmodel` objects as +seen in the example. + +```python +from typing import List, Optional + +import sqlmodel + +import reflex as rx + + +class Post(rx.Model, table=True): + title: str + body: str + user_id: int = sqlmodel.Field(foreign_key="user.id") + + user: Optional["User"] = sqlmodel.Relationship(back_populates="posts") + flags: Optional[List["Flag"]] = sqlmodel.Relationship(back_populates="post") + + +class User(rx.Model, table=True): + username: str + email: str + + posts: List[Post] = sqlmodel.Relationship(back_populates="user") + flags: List["Flag"] = sqlmodel.Relationship(back_populates="user") + + +class Flag(rx.Model, table=True): + post_id: int = sqlmodel.Field(foreign_key="post.id") + user_id: int = sqlmodel.Field(foreign_key="user.id") + message: str + + post: Optional[Post] = sqlmodel.Relationship(back_populates="flags") + user: Optional[User] = sqlmodel.Relationship(back_populates="flags") +``` + +See the [SQLModel Relationship Docs](https://sqlmodel.tiangolo.com/tutorial/relationship-attributes/define-relationships-attributes/) for more details. + +## Querying Relationships + +### Inserting Linked Objects + +The following example assumes that the flagging user is stored in the state as a +`User` instance and that the post `id` is provided in the data submitted in the +form. + +```python +class FlagPostForm(rx.State): + user: User + + def flag_post(self, form_data: dict[str, str]): + with rx.session() as session: + post = session.get(Post, int(form_data.pop("post_id"))) + flag = Flag(message=form_data.pop("message"), post=post, user=self.user) + session.add(flag) + session.commit() +``` + +### How are Relationships Dereferenced? + +By default, the relationship attributes are in **lazy loading** or `"select"` +mode, which generates a query _on access_ to the relationship attribute. Lazy +loading is generally fine for single object lookups and manipulation, but can be +inefficient when accessing many linked objects for serialization purposes. + +There are several alternative loading mechanisms available that can be set on +the relationship object or when performing the query. + +* "joined" or `joinload` - generates a single query to load all related objects + at once. +* "subquery" or `subqueryload` - generates a single query to load all related + objects at once, but uses a subquery to do the join, instead of a join in the + main query. +* "selectin" or `selectinload` - emits a second (or more) SELECT statement which + assembles the primary key identifiers of the parent objects into an IN clause, + so that all members of related collections / scalar references are loaded at + once by primary key + +There are also non-loading mechanisms, "raise" and "noload" which are used to +specifically avoid loading a relationship. + +Each loading method comes with tradeoffs and some are better suited for different +data access patterns. +See [SQLAlchemy: Relationship Loading Techniques](https://docs.sqlalchemy.org/en/14/orm/loading_relationships.html) +for more detail. + +### Querying Linked Objects + +To query the `Post` table and include all `User` and `Flag` objects up front, +the `.options` interface will be used to specify `selectinload` for the required +relationships. Using this method, the linked objects will be available for +rendering in frontend code without additional steps. + +```python +import sqlalchemy + + +class PostState(rx.State): + posts: List[Post] + + def load_posts(self): + with rx.session() as session: + self.posts = session.exec( + Post.select + .options( + sqlalchemy.orm.selectinload(Post.user), + sqlalchemy.orm.selectinload(Post.flags).options( + sqlalchemy.orm.selectinload(Flag.user), + ), + ) + .limit(15) + ).all() +``` + +The loading methods create new query objects and thus may be linked if the +relationship itself has other relationships that need to be loaded. In this +example, since `Flag` references `User`, the `Flag.user` relationship must be +chain loaded from the `Post.flags` relationship. + +### Specifying the Loading Mechanism on the Relationship + +Alternatively, the loading mechanism can be specified on the relationship by +passing `sa_relationship_kwargs=\{"lazy": method}` to `sqlmodel.Relationship`, +which will use the given loading mechanism in all queries by default. + +```python +from typing import List, Optional + +import sqlmodel + +import reflex as rx + + +class Post(rx.Model, table=True): + ... + user: Optional["User"] = sqlmodel.Relationship( + back_populates="posts", + sa_relationship_kwargs=\{"lazy": "selectin"}, + ) + flags: Optional[List["Flag"]] = sqlmodel.Relationship( + back_populates="post", + sa_relationship_kwargs=\{"lazy": "selectin"}, + ) +``` diff --git a/docs/database/tables.md b/docs/database/tables.md new file mode 100644 index 000000000..4292ccaa3 --- /dev/null +++ b/docs/database/tables.md @@ -0,0 +1,70 @@ +# Tables + +Tables are database objects that contain all the data in a database. + +In tables, data is logically organized in a row-and-column format similar to a +spreadsheet. Each row represents a unique record, and each column represents a +field in the record. + +## Creating a Table + +To create a table make a class that inherits from `rx.Model`. + +The following example shows how to create a table called `User`. + +```python +class User(rx.Model, table=True): + username: str + email: str +``` + +The `table=True` argument tells Reflex to create a table in the database for +this class. + +### Primary Key + +By default, Reflex will create a primary key column called `id` for each table. + +However, if an `rx.Model` defines a different field with `primary_key=True`, then the +default `id` field will not be created. A table may also redefine `id` as needed. + +It is not currently possible to create a table without a primary key. + +## Advanced Column Types + +SQLModel automatically maps basic python types to SQLAlchemy column types, but +for more advanced use cases, it is possible to define the column type using +`sqlalchemy` directly. For example, we can add a last updated timestamp to the +post example as a proper `DateTime` field with timezone. + +```python +import datetime + +import sqlmodel +import sqlalchemy + +class Post(rx.Model, table=True): + ... + update_ts: datetime.datetime = sqlmodel.Field( + default=None, + sa_column=sqlalchemy.Column( + "update_ts", + sqlalchemy.DateTime(timezone=True), + server_default=sqlalchemy.func.now(), + ), + ) +``` + +To make the `Post` model more usable on the frontend, a `dict` method may be provided +that converts any fields to a JSON serializable value. In this case, the dict method is +overriding the default `datetime` serializer to strip off the microsecond part. + +```python +class Post(rx.Model, table=True): + ... + + def dict(self, *args, **kwargs) -> dict: + d = super().dict(*args, **kwargs) + d["update_ts"] = self.update_ts.replace(microsecond=0).isoformat() + return d +``` diff --git a/docs/datatable_tutorial/__init__.py b/docs/datatable_tutorial/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/datatable_tutorial/add_interactivity.md b/docs/datatable_tutorial/add_interactivity.md new file mode 100644 index 000000000..f15900ffa --- /dev/null +++ b/docs/datatable_tutorial/add_interactivity.md @@ -0,0 +1,223 @@ +```python exec +import reflex as rx +from docs.datatable_tutorial.datatable_tutorial_utils import DataTableState, DataTableState2 +``` + +# Adding Interactivity to our DataTable + +Now we will add interactivity to our datatable. We do this using event handlers and event triggers. + +The first example implements a handler for the `on_cell_clicked` event trigger, which is called when the user clicks on a cell of the data editor. The event trigger receives the coordinates of the cell. + +```python +class DataTableState(rx.State): + clicked_cell: str = "Cell clicked: " + + ... + + + def get_clicked_data(self, pos: tuple[int, int]) -> str: + self.clicked_cell = f"Cell clicked: \{pos}" + +``` + +The state has a var called `clicked_cell` that will store a message about which cell was clicked. We define an event handler `get_clicked_data` that updates the value of the `clicked_cell` var when it is called. In essence, we have clicked on a cell, called the `on_cell_clicked` event trigger which calls the `get_clicked_data` event handler, which updates the `clicked_cell` var. + +```python demo +rx.text(DataTableState.clicked_cell) +``` + +```python demo +rx.data_editor( + columns=DataTableState.cols, + data=DataTableState.data, + on_cell_clicked=DataTableState.get_clicked_data, +) +``` + +The event handler `on_cell_context_menu` can be used in the same way as `on_cell_clicked`, except here the event trigger is called when the user right clicks, i.e. when the cell should show a context menu. + +## Editing cells + +Another important type of interactivity we will showcase is how to edit cells. Here we use the `on_cell_edited` event trigger to update the data based on what the user entered. + +```python +class DataTableState(rx.State): + clicked_cell: str = "Cell clicked: " + edited_cell: str = "Cell edited: " + + ... + + + def get_clicked_data(self, pos) -> str: + self.clicked_cell = f"Cell clicked: \{pos}" + + def get_edited_data(self, pos, val) -> str: + col, row = pos + self.data[row][col] = val["data"] + self.edited_cell = f"Cell edited: \{pos}, Cell value: \{val["data"]}" + +``` + +The `on_cell_edited` event trigger is called when the user modifies the content of a cell. It receives the coordinates of the cell and the modified content. We pass these into the `get_edited_data` event handler and use them to update the `data` state var at the appropriate position. We then update the `edited_cell` var value. + +```python demo +rx.text(DataTableState.edited_cell) +``` + +```python demo +rx.data_editor( + columns=DataTableState.cols, + data=DataTableState.data, + on_cell_clicked=DataTableState.get_clicked_data, + on_cell_edited=DataTableState.get_edited_data, +) +``` + +## Group Header + +We can define group headers which are headers that encompass a group of columns. We define these in the `columns` using the `group` property such as `"group": "Data"`. The `columns` would now be defined as below. Only the `Title` does not fall under a group header, all the rest fall under the `Data` group header. + +```python +class DataTableState2(rx.State): + """The app state.""" + + ... + + cols: list[dict] = [ + {\"title": "Title", "type": "str"}, + { + "title": "Name", + "type": "str", + "group": "Data", + "width": 300, + }, + { + "title": "Birth", + "type": "str", + "group": "Data", + "width": 150, + }, + { + "title": "Human", + "type": "bool", + "group": "Data", + "width": 80, + }, + { + "title": "House", + "type": "str", + "group": "Data", + }, + { + "title": "Wand", + "type": "str", + "group": "Data", + "width": 250, + }, + { + "title": "Patronus", + "type": "str", + "group": "Data", + }, + { + "title": "Blood status", + "type": "str", + "group": "Data", + "width": 200, + }, + ] + + ... +``` + +The table now has a header as below. + +```python demo +rx.data_editor( + columns=DataTableState2.cols, + data=DataTableState2.data, + on_cell_clicked=DataTableState2.get_clicked_data, + on_cell_edited=DataTableState2.get_edited_data, +) +``` + +There are several event triggers we can apply to the group header. + +```python +class DataTableState2(rx.State): + """The app state.""" + + right_clicked_group_header : str = "Group header right clicked: " + + ... + + def get_group_header_right_click(self, index, val): + self.right_clicked_group_header = f"Group header right clicked at index: \{index}, Group header value: \{val['group']}" + +``` + +```python demo +rx.text(DataTableState2.right_clicked_group_header) +``` + +```python demo +rx.data_editor( + columns=DataTableState2.cols, + data=DataTableState2.data, + on_cell_clicked=DataTableState2.get_clicked_data, + on_cell_edited=DataTableState2.get_edited_data, + on_group_header_context_menu=DataTableState2.get_group_header_right_click, +) +``` + +In this example we use the `on_group_header_context_menu` event trigger which is called when the user right-clicks on a group header. It returns the `index` and the `data` of the group header. We can also use the `on_group_header_clicked` and `on_group_header_renamed` event triggers which are called when the user left-clicks on a group header and when a user renames a group header respectively. + +## More Event Triggers + +There are several other event triggers that are worth exploring. The `on_item_hovered` event trigger is called whenever the user hovers over an item in the datatable. The `on_delete` event trigger is called when the user deletes a cell from the datatable. + +The final event trigger to check out is `on_column_resize`. `on_column_resize` allows us to respond to the user dragging the handle between columns. The event trigger returns the `col` we are adjusting and the new `width` we have defined. The `col` that is returned is a dictionary for example: `\{'title': 'Name', 'type': 'str', 'group': 'Data', 'width': 198, 'pos': 1}`. We then index into `self.cols` defined in our state and change the `width` of that column using this code: `self.cols[col['pos']]['width'] = width`. + +```python +class DataTableState2(rx.State): + """The app state.""" + + ... + + item_hovered: str = "Item Hovered: " + deleted: str = "Deleted: " + + ... + + + def get_item_hovered(self, pos) -> str: + self.item_hovered = f"Item Hovered type: \{pos['kind']}, Location: \{pos['location']}" + + def get_deleted_item(self, selection): + self.deleted = f"Deleted cell: \{selection['current']['cell']}" + + def column_resize(self, col, width): + self.cols[col['pos']]['width'] = width +``` + +```python demo +rx.text(DataTableState2.item_hovered) +``` + +```python demo +rx.text(DataTableState2.deleted) +``` + +```python demo +rx.data_editor( + columns=DataTableState2.cols, + data=DataTableState2.data, + on_cell_clicked=DataTableState2.get_clicked_data, + on_cell_edited=DataTableState2.get_edited_data, + on_group_header_context_menu=DataTableState2.get_group_header_right_click, + on_item_hovered=DataTableState2.get_item_hovered, + on_delete=DataTableState2.get_deleted_item, + on_column_resize=DataTableState2.column_resize, +) +``` diff --git a/docs/datatable_tutorial/add_styling.md b/docs/datatable_tutorial/add_styling.md new file mode 100644 index 000000000..79a263816 --- /dev/null +++ b/docs/datatable_tutorial/add_styling.md @@ -0,0 +1,141 @@ +```python exec +import reflex as rx +from docs.datatable_tutorial.datatable_tutorial_utils import DataTableState, DataTableState2 +from pcweb.pages.docs import library +``` + +# DataTable Styling + +There are props that we can explore to ensure the datatable is shaped correctly and reacts in the way we expect. We can set `on_paste` to `True`, which allows us to paste directly into a cell. We can use `draw_focus_ring` to draw a ring around the cells when selected, this defaults to `True` so can be turned off if we do not want it. The `rows` prop can be used to hard code the number of rows that we show. + +`freeze_columns` is used to keep a certain number of the left hand columns frozen when scrolling horizontally. `group_header_height` and `header_height` define the height of the group header and the individual headers respectively. `max_column_width` and `min_column_width` define how large or small the columns are allowed to be with the manual column resizing. We can also define the `row_height` to make the rows more nicely spaced. + +We can add `row_markers`, which appear on the furthest left side of the table. They can take values of `'none', 'number', 'checkbox', 'both', 'clickable-number'`. We can set `smooth_scroll_x` and `smooth_scroll_y`, which allows us to smoothly scroll along the columns and rows. + +By default there is a `vertical_border` between the columns, we can turn it off by setting this prop to `False`. We can define how many columns a user can select at a time by setting the `column_select` prop. It can take values of `"none", "single", "multi"`. + +We can allow `overscroll_x`, which allows users to scroll past the limit of the actual horizontal content. There is an equivalent `overscroll_y`. + +Check out [these docs]({library.datadisplay.data_editor.path}) for more information on these props. + +```python demo +rx.data_editor( + columns=DataTableState2.cols, + data=DataTableState2.data, + + #rows=4, + on_paste=True, + draw_focus_ring=False, + freeze_columns=2, + group_header_height=50, + header_height=60, + max_column_width=300, + min_column_width=100, + row_height=50, + row_markers='clickable-number', + smooth_scroll_x=True, + vertical_border=False, + column_select="multi", + overscroll_x=100, + + + on_cell_clicked=DataTableState2.get_clicked_data, + on_cell_edited=DataTableState2.get_edited_data, + on_group_header_context_menu=DataTableState2.get_group_header_right_click, + on_item_hovered=DataTableState2.get_item_hovered, + on_delete=DataTableState2.get_deleted_item, + on_column_resize=DataTableState2.column_resize, +) +``` + +## Theming + +Lastly there is a `theme` prop that allows us to pass in all color and font information for the data table. + +```python +darkTheme = { + "accentColor": "#8c96ff", + "accentLight": "rgba(202, 206, 255, 0.253)", + "textDark": "#ffffff", + "textMedium": "#b8b8b8", + "textLight": "#a0a0a0", + "textBubble": "#ffffff", + "bgIconHeader": "#b8b8b8", + "fgIconHeader": "#000000", + "textHeader": "#a1a1a1", + "textHeaderSelected": "#000000", + "bgCell": "#16161b", + "bgCellMedium": "#202027", + "bgHeader": "#212121", + "bgHeaderHasFocus": "#474747", + "bgHeaderHovered": "#404040", + "bgBubble": "#212121", + "bgBubbleSelected": "#000000", + "bgSearchResult": "#423c24", + "borderColor": "rgba(225,225,225,0.2)", + "drilldownBorder": "rgba(225,225,225,0.4)", + "linkColor": "#4F5DFF", + "headerFontStyle": "bold 14px", + "baseFontStyle": "13px", + "fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", +} +``` + +```python exec +darkTheme = { + "accentColor": "#8c96ff", + "accentLight": "rgba(202, 206, 255, 0.253)", + "textDark": "#ffffff", + "textMedium": "#b8b8b8", + "textLight": "#a0a0a0", + "textBubble": "#ffffff", + "bgIconHeader": "#b8b8b8", + "fgIconHeader": "#000000", + "textHeader": "#a1a1a1", + "textHeaderSelected": "#000000", + "bgCell": "#16161b", + "bgCellMedium": "#202027", + "bgHeader": "#212121", + "bgHeaderHasFocus": "#474747", + "bgHeaderHovered": "#404040", + "bgBubble": "#212121", + "bgBubbleSelected": "#000000", + "bgSearchResult": "#423c24", + "borderColor": "rgba(225,225,225,0.2)", + "drilldownBorder": "rgba(225,225,225,0.4)", + "linkColor": "#4F5DFF", + "headerFontStyle": "bold 14px", + "baseFontStyle": "13px", + "fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", +} +``` + +```python demo +rx.data_editor( + columns=DataTableState2.cols, + data=DataTableState2.data, + + on_paste=True, + draw_focus_ring=False, + freeze_columns=2, + group_header_height=50, + header_height=60, + max_column_width=300, + min_column_width=100, + row_height=50, + row_markers='clickable-number', + smooth_scroll_x=True, + vertical_border=False, + column_select="multi", + overscroll_x=100, + theme=darkTheme, + + + on_cell_clicked=DataTableState2.get_clicked_data, + on_cell_edited=DataTableState2.get_edited_data, + on_group_header_context_menu=DataTableState2.get_group_header_right_click, + on_item_hovered=DataTableState2.get_item_hovered, + on_delete=DataTableState2.get_deleted_item, + on_column_resize=DataTableState2.column_resize, +) +``` diff --git a/docs/datatable_tutorial/datatable_tutorial_utils.py b/docs/datatable_tutorial/datatable_tutorial_utils.py new file mode 100644 index 000000000..54317e393 --- /dev/null +++ b/docs/datatable_tutorial/datatable_tutorial_utils.py @@ -0,0 +1,328 @@ +import asyncio +from typing import Any + +import httpx + +import reflex as rx + + +class DataTableState(rx.State): + """The app state.""" + + clicked_cell: str = "Cell clicked: " + edited_cell: str = "Cell edited: " + + cols: list[dict] = [ + {"title": "Title", "type": "str"}, + { + "title": "Name", + "type": "str", + "width": 300, + }, + { + "title": "Birth", + "type": "str", + "width": 150, + }, + { + "title": "Human", + "type": "bool", + "width": 80, + }, + { + "title": "House", + "type": "str", + }, + { + "title": "Wand", + "type": "str", + "width": 250, + }, + { + "title": "Patronus", + "type": "str", + }, + { + "title": "Blood status", + "type": "str", + "width": 200, + }, + ] + + data = [ + [ + "1", + "Harry James Potter", + "31 July 1980", + True, + "Gryffindor", + "11' Holly phoenix feather", + "Stag", + "Half-blood", + ], + [ + "2", + "Ronald Bilius Weasley", + "1 March 1980", + True, + "Gryffindor", + "12' Ash unicorn tail hair", + "Jack Russell terrier", + "Pure-blood", + ], + [ + "3", + "Hermione Jean Granger", + "19 September, 1979", + True, + "Gryffindor", + "10¾' vine wood dragon heartstring", + "Otter", + "Muggle-born", + ], + [ + "4", + "Albus Percival Wulfric Brian Dumbledore", + "Late August 1881", + True, + "Gryffindor", + "15' Elder Thestral tail hair core", + "Phoenix", + "Half-blood", + ], + [ + "5", + "Rubeus Hagrid", + "6 December 1928", + False, + "Gryffindor", + "16' Oak unknown core", + "None", + "Part-Human (Half-giant)", + ], + [ + "6", + "Fred Weasley", + "1 April, 1978", + True, + "Gryffindor", + "Unknown", + "Unknown", + "Pure-blood", + ], + [ + "7", + "George Weasley", + "1 April, 1978", + True, + "Gryffindor", + "Unknown", + "Unknown", + "Pure-blood", + ], + ] + + def get_clicked_data(self, pos) -> str: + self.clicked_cell = f"Cell clicked: {pos}" + + def get_edited_data(self, pos, val) -> str: + col, row = pos + self.data[row][col] = val["data"] + self.edited_cell = f"Cell edited: {pos}, Cell value: {val['data']}" + + +class DataTableState2(rx.State): + """The app state.""" + + clicked_cell: str = "Cell clicked: " + edited_cell: str = "Cell edited: " + right_clicked_group_header: str = "Group header right clicked: " + item_hovered: str = "Item Hovered: " + deleted: str = "Deleted: " + + cols: list[dict] = [ + { + "title": "Title", + "type": "str", + "width": 100, + }, + { + "title": "Name", + "type": "str", + "group": "Data", + "width": 200, + }, + { + "title": "Birth", + "type": "str", + "group": "Data", + "width": 150, + }, + { + "title": "Human", + "type": "bool", + "group": "Data", + "width": 80, + }, + { + "title": "House", + "type": "str", + "group": "Data", + }, + { + "title": "Wand", + "type": "str", + "group": "Data", + "width": 250, + }, + { + "title": "Patronus", + "type": "str", + "group": "Data", + }, + { + "title": "Blood status", + "type": "str", + "group": "Data", + "width": 200, + }, + ] + + data = [ + [ + "1", + "Harry James Potter", + "31 July 1980", + True, + "Gryffindor", + "11' Holly phoenix feather", + "Stag", + "Half-blood", + ], + [ + "2", + "Ronald Bilius Weasley", + "1 March 1980", + True, + "Gryffindor", + "12' Ash unicorn tail hair", + "Jack Russell terrier", + "Pure-blood", + ], + [ + "3", + "Hermione Jean Granger", + "19 September, 1979", + True, + "Gryffindor", + "10¾' vine wood dragon heartstring", + "Otter", + "Muggle-born", + ], + [ + "4", + "Albus Percival Wulfric Brian Dumbledore", + "Late August 1881", + True, + "Gryffindor", + "15' Elder Thestral tail hair core", + "Phoenix", + "Half-blood", + ], + [ + "5", + "Rubeus Hagrid", + "6 December 1928", + False, + "Gryffindor", + "16' Oak unknown core", + "None", + "Part-Human (Half-giant)", + ], + [ + "6", + "Fred Weasley", + "1 April, 1978", + True, + "Gryffindor", + "Unknown", + "Unknown", + "Pure-blood", + ], + [ + "7", + "George Weasley", + "1 April, 1978", + True, + "Gryffindor", + "Unknown", + "Unknown", + "Pure-blood", + ], + ] + + def get_clicked_data(self, pos) -> str: + self.clicked_cell = f"Cell clicked: {pos}" + + def get_edited_data(self, pos, val) -> str: + col, row = pos + self.data[row][col] = val["data"] + self.edited_cell = f"Cell edited: {pos}, Cell value: {val['data']}" + + def get_group_header_right_click(self, index, val): + self.right_clicked_group_header = f"Group header right clicked at index: {index}, Group header value: {val['group']}" + + def get_item_hovered(self, pos) -> str: + self.item_hovered = ( + f"Item Hovered type: {pos['kind']}, Location: {pos['location']}" + ) + + def get_deleted_item(self, selection): + self.deleted = f"Deleted cell: {selection['current']['cell']}" + + def column_resize(self, col, width): + self.cols[col["pos"]]["width"] = width + + +class DataTableLiveState(rx.State): + "The app state." + + running: bool = False + table_data: list[dict[str, Any]] = [] + rate: int = 0.4 + columns: list[dict[str, str]] = [ + { + "title": "id", + "id": "v1", + "type": "int", + "width": 100, + }, + { + "title": "advice", + "id": "v2", + "type": "str", + "width": 750, + }, + ] + + @rx.background + async def live_stream(self): + while True: + await asyncio.sleep(1 / self.rate) + if not self.running: + break + + async with self: + if len(self.table_data) > 50: + self.table_data.pop(0) + + res = httpx.get("https://api.adviceslip.com/advice") + data = res.json() + self.table_data.append( + {"v1": data["slip"]["id"], "v2": data["slip"]["advice"]} + ) + + def toggle_pause(self): + self.running = not self.running + if self.running: + return DataTableLiveState.live_stream diff --git a/docs/datatable_tutorial/live_stream.md b/docs/datatable_tutorial/live_stream.md new file mode 100644 index 000000000..e2a2c01aa --- /dev/null +++ b/docs/datatable_tutorial/live_stream.md @@ -0,0 +1,108 @@ +```python exec +import reflex as rx +from docs.datatable_tutorial.datatable_tutorial_utils import DataTableLiveState + +darkTheme = { + "accentColor": "#8c96ff", + "accentLight": "rgba(202, 206, 255, 0.253)", + "textDark": "#ffffff", + "textMedium": "#b8b8b8", + "textLight": "#a0a0a0", + "textBubble": "#ffffff", + "bgIconHeader": "#b8b8b8", + "fgIconHeader": "#000000", + "textHeader": "#a1a1a1", + "textHeaderSelected": "#000000", + "bgCell": "#16161b", + "bgCellMedium": "#202027", + "bgHeader": "#212121", + "bgHeaderHasFocus": "#474747", + "bgHeaderHovered": "#404040", + "bgBubble": "#212121", + "bgBubbleSelected": "#000000", + "bgSearchResult": "#423c24", + "borderColor": "rgba(225,225,225,0.2)", + "drilldownBorder": "rgba(225,225,225,0.4)", + "linkColor": "#4F5DFF", + "headerFontStyle": "bold 14px", + "baseFontStyle": "13px", + "fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", +} +``` + +# Live Streaming example + +Lastly let's add in an API so we can live stream data into our datatable. + +Here we use a [Background Task](https://reflex.dev/docs/advanced-guide/background-tasks) to stream the data into the table without blocking UI interactivity. We call an advice API using `httpx` and then append that data to the `self.table_data` state var. We also create a button that allows us to start and pause the streaming of the data by changing the value of the boolean state var `running` using the event handler `toggle_pause`. If the `running` state var is set to `True` we stream the API data, when it is set to `False` we break out of the `while` loop and end the background event. + +```python +class DataTableLiveState(BaseState): + "The app state." + + running: bool = False + table_data: list[dict[str, Any]] = [] + rate: int = 0.4 + columns: list[dict[str, str]] = [ + { + "title": "id", + "id": "v1", + "type": "int", + "width": 100, + }, + { + "title": "advice", + "id": "v2", + "type": "str", + "width": 750, + }, + + ] + + @rx.background + async def live_stream(self): + while True: + await asyncio.sleep(1 / self.rate) + if not self.running: + break + + async with self: + if len(self.table_data) > 50: + self.table_data.pop(0) + + res = httpx.get('https://api.adviceslip.com/advice') + data = res.json() + self.table_data.append(\{"v1": data["slip"]["id"], "v2": data["slip"]["advice"]}) + + + def toggle_pause(self): + self.running = not self.running + if self.running: + return DataTableLiveState.live_stream +``` + +```python demo +rx.vstack( + rx.stack( + rx.cond( + ~DataTableLiveState.running, + rx.button("Start", on_click=DataTableLiveState.toggle_pause, color_scheme='green'), + rx.button("Pause", on_click=DataTableLiveState.toggle_pause, color_scheme='red'), + ), + ), + rx.data_editor( + columns=DataTableLiveState.columns, + data=DataTableLiveState.table_data, + draw_focus_ring=True, + row_height=50, + smooth_scroll_x=True, + smooth_scroll_y=True, + column_select="single", + # style + theme=darkTheme, + ), + overflow_x="auto", + width="100%", + height="30vh", +) +``` diff --git a/docs/datatable_tutorial/simple_table.md b/docs/datatable_tutorial/simple_table.md new file mode 100644 index 000000000..0a929f7be --- /dev/null +++ b/docs/datatable_tutorial/simple_table.md @@ -0,0 +1,87 @@ +```python exec +import reflex as rx +from docs.datatable_tutorial.datatable_tutorial_utils import DataTableState, DataTableState2 +from pcweb.pages.docs import library +``` + +# Data Table (Editable) Tutorial + +```md alert info +#There is another [datatable component]({library.datadisplay.datatable.path}), which is only used for displaying data and does not support user interactivity or editing. +``` + +```python eval +rx.box(height="2em") +``` + +We need to start by defining our columns that describe the shape of our data. The column var should be typed as a `list` of `dict` (`list[dict]`), where each item describes the attributes of a single column in the table. + +Each column dict recognizes the keys below: + +1. `title`: The text to display in the header of the column +2. `id`: An id for the column, if not defined, will default to a lower case of title +3. `width`: The width of the column (in pixels) +4. `type`: The type of the columns, default to "str" + +Below we define `DataTableState` with columns definitions in the `cols` var, and data about Harry Potter characters in the `data` var.. + +```python +class DataTableState(rx.State): + """The app state.""" + cols: list[dict] = [ + {\"title": "Title", "type": "str"}, + { + "title": "Name", + "type": "str", + "width": 300, + }, + { + "title": "Birth", + "type": "str", + "width": 150, + }, + { + "title": "Human", + "type": "bool", + "width": 80, + }, + { + "title": "House", + "type": "str", + }, + { + "title": "Wand", + "type": "str", + "width": 250, + }, + { + "title": "Patronus", + "type": "str", + }, + { + "title": "Blood status", + "type": "str", + "width": 200, + }, + ] + data = [ + ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"], + ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"], + ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], + ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], + ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], + ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], + ["7", "George Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], + ] +``` + +We then define a basic table by passing the previously defined state vars as props `columns` and `data` to the `rx.data_editor()` component, + +```python demo +rx.data_editor( + columns=DataTableState.cols, + data=DataTableState.data, +) +``` + +This is enough to display the data, but there is no way to interact with it. On the next page we will explore how to add interactivity to our datatable. diff --git a/docs/events/background_events.md b/docs/events/background_events.md new file mode 100644 index 000000000..8c7c29d9b --- /dev/null +++ b/docs/events/background_events.md @@ -0,0 +1,175 @@ +```python exec +import reflex as rx +from pcweb import constants, styles +``` + +# Background Tasks + +A background task is a special type of `EventHandler` that may run +concurrently with other `EventHandler` functions. This enables long-running +tasks to execute without blocking UI interactivity. + +A background task is defined by decorating an async `State` method with +`@rx.background`. + +Whenever a background task needs to interact with the state, **it must enter an +`async with self` context block** which refreshes the state and takes an +exclusive lock to prevent other tasks or event handlers from modifying it +concurrently. Because other `EventHandler` functions may modify state while the +task is running, **outside of the context block, Vars accessed by the background +task may be _stale_**. Attempting to modify the state from a background task +outside of the context block will raise an `ImmutableStateError` exception. + +In the following example, the `my_task` event handler is decorated with +`@rx.background` and increments the `counter` variable every half second, as +long as certain conditions are met. While it is running, the UI remains +interactive and continues to process events normally. + +```python demo exec +import asyncio +import reflex as rx + + +class MyTaskState(rx.State): + counter: int = 0 + max_counter: int = 10 + running: bool = False + _n_tasks: int = 0 + + @rx.background + async def my_task(self): + async with self: + # The latest state values are always available inside the context + if self._n_tasks > 0: + # only allow 1 concurrent task + return + + # State mutation is only allowed inside context block + self._n_tasks += 1 + + while True: + async with self: + # Check for stopping conditions inside context + if self.counter >= self.max_counter: + self.running = False + if not self.running: + self._n_tasks -= 1 + return + + self.counter += 1 + + # Await long operations outside the context to avoid blocking UI + await asyncio.sleep(0.5) + + def toggle_running(self): + self.running = not self.running + if self.running: + return MyTaskState.my_task + + def clear_counter(self): + self.counter = 0 + + +def background_task_example(): + return rx.hstack( + rx.heading(MyTaskState.counter, " /"), + rx.chakra.number_input( + value=MyTaskState.max_counter, + on_change=MyTaskState.set_max_counter, + width="8em", + ), + rx.button( + rx.cond(~MyTaskState.running, "Start", "Stop"), + on_click=MyTaskState.toggle_running, + ), + rx.button( + "Reset", + on_click=MyTaskState.clear_counter, + ), + ) +``` + +## Task Lifecycle + +When a background task is triggered, it starts immediately, saving a reference to +the task in `app.background_tasks`. When the task completes, it is removed from +the set. + +Multiple instances of the same background task may run concurrently, and the +framework makes no attempt to avoid duplicate tasks from starting. + +It is up to the developer to ensure that duplicate tasks are not created under +the circumstances that are undesirable. In the example above, the `_n_tasks` +backend var is used to control whether `my_task` will enter the increment loop, +or exit early. + +## Background Task Limitations + +Background tasks mostly work like normal `EventHandler` methods, with certain exceptions: + +* Background tasks must be `async` functions. +* Background tasks cannot modify the state outside of an `async with self` context block. +* Background tasks may read the state outside of an `async with self` context block, but the value may be stale. +* Background tasks may not be directly called from other event handlers or background tasks. Instead use `yield` or `return` to trigger the background task. + +## Low-level API + +The `@rx.background` decorator is a convenience wrapper around the lower-level +`App.modify_state` async contextmanager. If more control over task lifecycle is +needed, arbitrary async tasks may safely manipulate the state using an +`async with app.modify_state(token) as state` context block. In this case the +`token` for a state is retrieved from `state.get_token()` and identifies a +single instance of the state (i.e. the state for an individual browser tab). + +Care must be taken to **never directly modify the state outside of the +`modify_state` contextmanager**. If the code that creates the task passes a +direct reference to the state instance, this can introduce subtle bugs or not +work at all (if redis is used for state storage). + +The following example creates an arbitrary `asyncio.Task` to fetch data and then +uses the low-level API to safely update the state and send the changes to the +frontend. + +```python demo exec +import asyncio +import httpx +import reflex as rx + +my_tasks = set() + + +async def _fetch_data(app, token): + async with httpx.AsyncClient() as client: + response = await client.get("https://api.github.com/zen") + async with app.modify_state(token) as state: + substate = state.get_substate( + LowLevelState.get_full_name().split("."), + ) + substate.result = response.text + + +class LowLevelState(rx.State): + result: str = "" + + def fetch_data(self): + task = asyncio.create_task( + _fetch_data( + app=rx.utils.prerequisites.get_app().app, + token=self.get_token(), + ), + ) + + # Always save a reference to your tasks until they are done + my_tasks.add(task) + task.add_done_callback(my_tasks.discard) + + +def low_level_example(): + return rx.vstack( + rx.text(LowLevelState.result), + rx.button( + "Fetch Data", + on_click=LowLevelState.fetch_data, + ), + ) +``` diff --git a/docs/events/chaining_events.md b/docs/events/chaining_events.md new file mode 100644 index 000000000..80ac09d1b --- /dev/null +++ b/docs/events/chaining_events.md @@ -0,0 +1,90 @@ +```python exec +import reflex as rx +``` + +# Chaining events + +## Calling Event Handlers From Event Handlers + +You can call other event handlers from event handlers to keep your code modular. Just use the `self.call_handler` syntax to run another event handler. As always, you can yield within your function to send incremental updates to the frontend. + +```python demo exec +import asyncio + +class CallHandlerState(rx.State): + count: int = 0 + progress: int = 0 + + async def run(self): + # Reset the count. + self.set_progress(0) + yield + + # Count to 10 while showing progress. + for i in range(10): + # Wait and increment. + await asyncio.sleep(0.5) + self.count += 1 + + # Update the progress. + self.set_progress(i + 1) + + # Yield to send the update. + yield + + +def call_handler_example(): + return rx.vstack( + rx.badge(CallHandlerState.count, font_size="1.5em", color_scheme="green"), + rx.progress(value=CallHandlerState.progress, max=10, width="100%"), + rx.button("Run", on_click=CallHandlerState.run), + ) +``` + +## Returning Events From Event Handlers + +So far, we have only seen events that are triggered by components. However, an event handler can also return events. + +In Reflex, event handlers run synchronously, so only one event handler can run at a time, and the events in the queue will be blocked until the current event handler finishes.The difference between returning an event and calling an event handler is that returning an event will send the event to the frontend and unblock the queue. + +```md alert +Be sure to use the class name `State` (or any substate) rather than `self` when returning events. +``` + +Try entering an integer in the input below then clicking out. + +```python demo exec +class CollatzState(rx.State): + count: int = 0 + + def start_collatz(self, count: str): + """Run the collatz conjecture on the given number.""" + self.count = abs(int(count)) + return CollatzState.run_step + + async def run_step(self): + """Run a single step of the collatz conjecture.""" + + while self.count > 1: + await asyncio.sleep(0.5) + + if self.count % 2 == 0: + # If the number is even, divide by 2. + self.count /= 2 + else: + # If the number is odd, multiply by 3 and add 1. + self.count = self.count * 3 + 1 + yield + + +def collatz_example(): + return rx.vstack( + rx.badge(CollatzState.count, font_size="1.5em", color_scheme="green"), + rx.chakra.input(on_blur=CollatzState.start_collatz), + ) + +``` + +In this example, we run the [Collatz Conjecture](https://en.wikipedia.org/wiki/Collatz_conjecture) on a number entered by the user. + +When the `on_blur` event is triggered, the event handler `start_collatz` is called. It sets the initial count, then calls `run_step` which runs until the count reaches `1`. diff --git a/docs/events/event_arguments.md b/docs/events/event_arguments.md new file mode 100644 index 000000000..813c471f9 --- /dev/null +++ b/docs/events/event_arguments.md @@ -0,0 +1,34 @@ +```python exec +import reflex as rx +``` + +# Event Arguments + +In some use cases, you want to pass additional arguments to your event handlers. To do this you can bind an event trigger to a lambda, which can call your event handler with the arguments you want. + +Try typing a color in an input below and clicking away from it to change the color of the input. + +```python demo exec +class ArgState(rx.State): + colors: list[str] = ["rgba(222,44,12)", "white", "#007ac2"] + + def change_color(self, color: str, index: int): + self.colors[index] = color + +def event_arguments_example(): + return rx.hstack( + rx.chakra.input(default_value=ArgState.colors[0], on_blur=lambda c: ArgState.change_color(c, 0), bg=ArgState.colors[0]), + rx.chakra.input(default_value=ArgState.colors[1], on_blur=lambda c: ArgState.change_color(c, 1), bg=ArgState.colors[1]), + rx.chakra.input(default_value=ArgState.colors[2], on_blur=lambda c: ArgState.change_color(c, 2), bg=ArgState.colors[2]), + ) + +``` + +In this case, in we want to pass two arguments to the event handler `change_color`, the color and the index of the color to change. + +The `on_blur` event trigger passes the text of the input as an argument to the lambda, and the lambda calls the `change_color` event handler with the text and the index of the input. + +```md alert warning +# Event Handler Parameters should provide type annotations. +Like state vars, be sure to provide the right type annotations for the parameters in an event handler. +``` diff --git a/docs/events/events_overview.md b/docs/events/events_overview.md new file mode 100644 index 000000000..e9dceeed4 --- /dev/null +++ b/docs/events/events_overview.md @@ -0,0 +1,41 @@ +```python exec +import reflex as rx + +from pcweb.pages.docs.library import library +``` + +# Events Overview + +Events are how we modify the state and make the app interactive. + +Event triggers are component props that create an event to be sent to an event handler. +Each component supports a set of events triggers. They are described in each [component's documentation]({library.path}) in the event trigger section. + +Lets take a look at an example below. Try mousing over the heading to change the word. + +```python demo exec +class WordCycleState(rx.State): + # The words to cycle through. + text: list[str] = ["Welcome", "to", "Reflex", "!"] + + # The index of the current word. + index: int = 0 + + def next_word(self): + self.index = (self.index + 1) % len(self.text) + + @rx.var + def get_text(self) -> str: + return self.text[self.index] + +def event_triggers_example(): + return rx.heading( + WordCycleState.get_text, + on_mouse_over=WordCycleState.next_word, + color="green", + ) + +``` + +In this example, the heading component has the event trigger, `on_mouse_over`. +Whenever the user hovers over the heading, the `next_word` handler will be called to cycle the word. Once the handler returns, the UI will be updated to reflect the new state. diff --git a/docs/events/page_load_events.md b/docs/events/page_load_events.md new file mode 100644 index 000000000..37b9a583e --- /dev/null +++ b/docs/events/page_load_events.md @@ -0,0 +1,21 @@ +```python exec +import reflex as rx +``` + +# Page Load Events + +You can also specify a function to run when the page loads. This can be useful for fetching data once vs on every render or state change. +In this example, we fetch data when the page loads: + +```python +class State(rx.State): + data: Dict[str, Any] + + def get_data(self): + # Fetch data + self.data = fetch_data() + +@rx.page(on_load=State.get_data) +def index(): + return rx.text('A Beautiful App') +``` diff --git a/docs/events/setters.md b/docs/events/setters.md new file mode 100644 index 000000000..123c46b80 --- /dev/null +++ b/docs/events/setters.md @@ -0,0 +1,52 @@ +```python exec +import reflex as rx +``` + +# Setters + +Every base var has a built-in event handler to set it's value for convenience, called `set_VARNAME`. + +Say you wanted to change the value of the select component. You could write your own event handler to do this: + +```python demo exec + +options: list[str] = ["1", "2", "3", "4"] +class SetterState1(rx.State): + selected: str = "1" + + def change(self, value): + self.selected = value + + +def code_setter(): + return rx.vstack( + rx.badge(SetterState1.selected, color_scheme="green"), + rx.select( + options, + on_change= lambda value: SetterState1.change(value), + ) + ) + +``` + +Or you could could use a built-in setter for conciseness. + +```python demo exec + +options: list[str] = ["1", "2", "3", "4"] +class SetterState2(rx.State): + selected: str = "1" + +def code_setter_2(): + return rx.vstack( + rx.badge(SetterState2.selected, color_scheme="green"), + rx.select( + options, + on_change= SetterState2.set_selected, + ) + ) +``` + +In this example, the setter for `selected` is `set_selected`. Both of these examples are equivalent. + +Setters are a great way to make your code more concise. But if you want to do something more complicated, you can always write your own function in the state. diff --git a/docs/events/special_events.md b/docs/events/special_events.md new file mode 100644 index 000000000..f6b58678f --- /dev/null +++ b/docs/events/special_events.md @@ -0,0 +1,20 @@ +```python exec +import reflex as rx + +from pcweb.pages.docs import api_reference +``` + +# Special Events + +Reflex also has built-in special events can be found in the [reference]({api_reference.special_events.path}). + +For example, an event handler can trigger an alert on the browser. + +```python demo exec +class SpecialEventsState(rx.State): + def alert(self): + return rx.window_alert("Hello World!") + +def special_events_example(): + return rx.button("Alert", on_click=SpecialEventsState.alert) +``` diff --git a/docs/events/yield_events.md b/docs/events/yield_events.md new file mode 100644 index 000000000..b5888c01b --- /dev/null +++ b/docs/events/yield_events.md @@ -0,0 +1,63 @@ +```python exec +import reflex as rx + +``` + +# Yielding Multiple Updates + +A regular event handler will send a `StateUpdate` when it has finished running. This works fine for basic event, but sometimes we need more complex logic. To update the UI multiple times in an event handler, we can `yield` when we want to send an update. + +To do so, we can use the Python keyword `yield`. For every yield inside the function, a `StateUpdate` will be sent to the frontend with the changes up to this point in the execution of the event handler. + +```python demo exec + +import asyncio + +class MultiUpdateState(rx.State): + count: int = 0 + + async def timed_update(self): + for i in range(5): + await asyncio.sleep(0.5) + self.count += 1 + yield + + +def multi_update(): + return rx.vstack( + rx.text(MultiUpdateState.count), + rx.button("Start", on_click=MultiUpdateState.timed_update) +) + +``` + +Here is another example of yielding multiple updates with a loading icon. + +```python demo exec + +import asyncio + +class ProgressExampleState(rx.State): + count: int = 0 + show_progress: bool = False + + async def increment(self): + self.show_progress = True + yield + # Think really hard. + await asyncio.sleep(0.5) + self.count += 1 + self.show_progress = False + +def progress_example(): + return rx.cond( + ProgressExampleState.show_progress, + rx.chakra.circular_progress(is_indeterminate=True), + rx.heading( + ProgressExampleState.count, + on_click=ProgressExampleState.increment, + _hover={"cursor": "pointer"}, + ) + ) + +``` diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md new file mode 100644 index 000000000..09d6164cd --- /dev/null +++ b/docs/getting-started/configuration.md @@ -0,0 +1,91 @@ +```python exec +config_api_ref_url = "/docs/api-reference/config" +cli_api_ref_url = "/docs/api-reference/cli" +``` + +# Configuration + +Reflex apps can be configured using a configuration file, environment variables, and command line arguments. + +## Configuration File + +Running `reflex init` will create an `rxconfig.py` file in your root directory. +You can pass keyword arguments to the `Config` class to configure your app. + +For example: + +```python +# rxconfig.py +import reflex as rx + +config = rx.Config( + app_name="my_app_name", + # Connect to your own database. + db_url="postgresql://user:password@localhost:5432/my_db", + # Change the frontend port. + frontend_port=3001, +) +``` + +See the [config reference]({config_api_ref_url}) for all the parameters available. + +## Environment Variables + +You can override the configuration file by setting environment variables. +For example, to override the `frontend_port` setting, you can set the `FRONTEND_PORT` environment variable. + +```bash +FRONTEND_PORT=3001 reflex run +``` + +## Command Line Arguments + +Finally, you can override the configuration file and environment variables by passing command line arguments to `reflex run`. + +```bash +reflex run --frontend-port 3001 +``` + +See the [CLI reference]({cli_api_ref_url}) for all the arguments available. + +## Anonymous Usage Statistics + +Reflex collects completely anonymous telemetry data about general usage. +Participation in this anonymous program is optional, and you may opt-out if you'd not like to share any information. + +### What's Being Collected + +Telemetry allows us to understand how Reflex is used, what features are most important, and how we can improve the product. + +The following information is collected: + +* Operating system +* CPU count +* Memoryd +* Python version +* Reflex version + +### How to Opt-Out + +To disable telemetry, set `telemetry_enabled=False` in your `rxconfig.py` file. + +```python +config = rx.Config( + app_name="hello", + telemetry_enabled=False, +) +``` + +Alternatively, you can set the `TELEMETRY_ENABLED` environment variable to `False`. + +## Customizable App Data Directory + +The `REFLEX_DIR` environment variable can be set, which allows users to set the location where Reflex writes helper tools like Bun and NodeJS. + +By default we use Platform specific directories: + +On windows, `C:/Users//AppData/Local/reflex` is used. + +On macOS, `~/Library/Application Support/reflex` is used. + +On linux, `~/.local/share/reflex` is used. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 000000000..c8e5fd971 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,147 @@ +```python exec +from pcweb import constants +import reflex as rx +app_name = "my_app_name" +default_url = "http://localhost:3000" +``` + +# Installation + +Reflex requires Python 3.8+. + +## Virtual Environment + +We **highly recommend** creating a virtual environment for your project. + +[venv]({constants.VENV_URL}) is the standard option. [conda]({constants.CONDA_URL}) and [poetry]({constants.POETRY_URL}) are some alternatives. + +## Install on macOS/Linux + +We will go with [venv]({constants.VENV_URL}) here. + + +### Prerequisites +macOS (Apple Silicon) users should install [Rosetta 2](https://support.apple.com/en-us/HT211861). Run this command: + +`/usr/sbin/softwareupdate --install-rosetta --agree-to-license` + + +### Create the project directory + +Replace `{app_name}` with your project name. Switch to the new directory. + +```bash +mkdir {app_name} +cd {app_name} +``` + +### Setup virtual environment + +```bash +python3 -m venv .venv +source .venv/bin/activate +``` + +```md alert warning +# Error `No module named venv` + +While Python typically ships with `venv` it is not installed by default on some systems. +If so, please install it manually. E.g. on Ubuntu Linux, run `sudo apt-get install python3-venv`. +``` + +### Install Reflex package + +Reflex is available as a [pip](constants.PIP_URL) package. + +```bash +pip install reflex +``` + +```md alert warning +# Error `command not found: pip` + +While Python typically ships with `pip` as the standard package management tool, it is not installed by default on some systems. +You may need to install it manually. E.g. on Ubuntu Linux, run `apt-get install python3-pip` +``` + +### Initialize the project + +```bash +reflex init +``` + +```md alert warning +# Error `command not found: reflex` +If you install Reflex with no virtual environment and get this error it means your `PATH` cannot find the reflex package. +A virtual environment should solve this problem, or you can try running `python3 -m` before the reflex command. +``` + + +## Install on Windows + +### Prerequisites +For Windows users, we recommend using [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/about) for optimal performance. + +WSL users should refer to instructions for Linux above. + +For the rest of this section we will work with native Windows (non-WSL). + +We will go with [venv]({constants.VENV_URL}) here, for virtual environments. + +### Create a new project directory + +```bash +mkdir {app_name} +cd {app_name} +``` + +### Setup virtual environment + +```bash +py -3 -m venv .venv +.venv\\Scripts\\activate +``` + +### Install Reflex package + +```bash +pip install reflex +``` + +### Initialize the project + +```bash +reflex init +``` + +```md alert warning +# Error `command not found: reflex` + +The Reflex framework includes the `reflex` command line (CLI) tool. Using a virtual environment is highly recommended for a seamless experience (see below).", +``` + +## Run the App + +Run it in development mode: + +```bash +reflex run +``` + +Your app runs at [http://localhost:3000](http://localhost:3000). + +Reflex prints logs to the terminal. To increase log verbosity to help with debugging, use the `--loglevel` flag: + +```bash +reflex run --loglevel debug +``` + +Reflex will *hot reload* any code changes in real time when running in development mode. Your code edits will show up on [http://localhost:3000](http://localhost:3000) automatically. + +## (Optional) Run the demo app + +The demo app showcases some of Reflex's features. + +```bash +reflex demo +``` diff --git a/docs/getting-started/introduction.md b/docs/getting-started/introduction.md new file mode 100644 index 000000000..ff40d228d --- /dev/null +++ b/docs/getting-started/introduction.md @@ -0,0 +1,226 @@ +```python exec +import reflex as rx +from pcweb import constants, styles +from pcweb.templates.docpage import doccode +from pcweb.pages.docs import tutorial +from pcweb.pages.docs import getting_started +from pcweb.pages.docs import wrapping_react +from pcweb.pages.docs.library import library +from pcweb.pages.docs import vars +``` + + +# Introduction + +**Reflex** is an open-source framework for quickly building beautiful, interactive web applications in **pure Python**. + +## Goals + +```md section +# Pure Python +Use Python for everything. Don't worry about learning a new language. + +# Easy to Learn +Build and share your first app in minutes. No web development experience required. + +# Full Flexibility +Remain as flexible as traditional web frameworks. Reflex is easy to use, yet allows for advanced use cases. + +Build anything from small data science apps to large, multi-page websites. **This entire site was built and deployed with Reflex!** + +# Batteries Included +No need to reach for a bunch of different tools. Reflex handles the user interface, server-side logic, and deployment of your app. +``` + +## An example: Make it count + +Here, we go over a simple counter app that lets the user count up or down. + + + + + +```python exec +class CounterExampleState(rx.State): + count: int = 0 + + def increment(self): + self.count += 1 + + def decrement(self): + self.count -= 1 + +``` + + +```python demo box +rx.hstack( + rx.button( + "Decrement", + color_scheme="ruby", + on_click=CounterExampleState.decrement, + ), + rx.heading(CounterExampleState.count, font_size="2em"), + rx.button( + "Increment", + color_scheme="grass", + on_click=CounterExampleState.increment, + ), + spacing="4", +) +``` + +Here is the full code for this example: + +```python +import reflex as rx + + +class State(rx.State): + count: int = 0 + + def increment(self): + self.count += 1 + + def decrement(self): + self.count -= 1 + +def index(): + return rx.hstack( + rx.button( + "Decrement", + color_scheme="ruby", + on_click=State.decrement, + ), + rx.heading(State.count, font_size="2em"), + rx.button( + "Increment", + color_scheme="grass", + on_click=State.increment, + ), + spacing="4", + ) + + +app = rx.App() +app.add_page(index) +``` + + +## The Structure of a Reflex App + +Let's break this example down. + +### Import + +```python +import reflex as rx +``` + +We begin by importing the `reflex` package (aliased to `rx`). We reference Reflex objects as `rx.*` by convention. + +### State + +```python +class State(rx.State): + count: int = 0 +``` + +The state defines all the variables (called **[vars]({vars.base_vars.path})**) in an app that can change, as well as the functions (called **[event_handlers](#event-handlers)**) that change them. + +Here our state has a single var, `count`, which holds the current value of the counter. We initialize it to `0`. + +### Event Handlers + +```python +def increment(self): + self.count += 1 + +def decrement(self): + self.count -= 1 +``` + +Within the state, we define functions, called **event handlers**, that change the state vars. + +Event handlers are the only way that we can modify the state in Reflex. +They can be called in response to user actions, such as clicking a button or typing in a text box. +These actions are called **events**. + +Our counter app has two event handlers, `increment` and `decrement`. + +### User Interface (UI) + +```python +def index(): + return rx.hstack( + rx.button( + "Decrement", + color_scheme="ruby", + on_click=State.decrement, + ), + rx.heading(State.count, font_size="2em"), + rx.button( + "Increment", + color_scheme="grass", + on_click=State.increment, + ), + spacing="4", + ) +``` + +This function defines the app's user interface. + +We use different components such as `rx.hstack`, `rx.button`, and `rx.heading` to build the frontend. Components can be nested to create complex layouts, and can be styled using the full power of CSS. + +Reflex comes with [50+ built-in components]({library.path}) to help you get started. +We are actively adding more components. Also, it's easy to [wrap your own React components]({wrapping_react.overview.path}). + +```python +rx.heading(State.count, font_size="2em"), +``` + +Components can reference the app's state vars. +The `rx.heading` component displays the current value of the counter by referencing `State.count`. +All components that reference state will reactively update whenever the state changes. + +```python +rx.button( + "Decrement", + color_scheme="ruby", + on_click=State.decrement, +), +``` + +Components interact with the state by binding events triggers to event handlers. +For example, `on_click` is an event that is triggered when a user clicks a component. + +The first button in our app binds its `on_click` event to the `State.decrement` event handler. Similarly the second button binds `on_click` to `State.increment`. + +In other words, the sequence goes like this: + +* User clicks "increment" on the UI. +* `on_click` event is triggered. +* Event handler `State.increment` is called. +* `State.count` is incremented. +* UI updates to reflect the new value of `State.count`. + +### Add pages + +Next we define our app and add the counter component to the base route. + +```python +app = rx.App() +app.add_page(index) +``` + +## Next Steps + +🎉 And that's it! + +We've created a simple, yet fully interactive web app in pure Python. + +By continuing with our documentation, you will learn how to building awesome apps with Reflex. + +For a glimpse of the possibilities, check out these resources: + +* For a more real-world example, check out the [tutorial]({tutorial.intro.path}). diff --git a/docs/getting-started/project-structure.md b/docs/getting-started/project-structure.md new file mode 100644 index 000000000..19d6ef34d --- /dev/null +++ b/docs/getting-started/project-structure.md @@ -0,0 +1,66 @@ +# Project Structure + +## Directory Structure + +```python exec +app_name = "hello" +``` + +Let's create a new app called `{app_name}` + +```bash +mkdir {app_name} +cd {app_name} +reflex init +``` + +This will create a directory structure like this: + +```bash +{app_name} +├── .web +├── assets +├── {app_name} +│ ├── __init__.py +│ └── {app_name}.py +└── rxconfig.py +``` + +Let's go over each of these directories and files. + +## .web + +This is where the compiled Javascript files will be stored. You will never need to touch this directory, but it can be useful for debugging. + +Each Reflex page will compile to a corresponding `.js` file in the `.web/pages` directory. + +## Assets + +The `assets` directory is where you can store any static assets you want to be publicly available. This includes images, fonts, and other files. + +For example, if you save an image to `assets/image.png` you can display it from your app like this: + +```python +rx.image(src="image.png") +``` + +## Main Project + +Initializing your project creates a directory with the same name as your app. This is where you will write your app's logic. + +Reflex generates a default app within the `{app_name}/{app_name}.py` file. You can modify this file to customize your app. + +## Configuration + +The `rxconfig.py` file can be used to configure your app. By default it looks something like this: + +```python +import reflex as rx + + +config = rx.Config( + app_name="{app_name}", +) +``` + +We will discuss configuration in more detail in the next section. diff --git a/docs/hosting/deploy-quick-start.md b/docs/hosting/deploy-quick-start.md new file mode 100644 index 000000000..64f82484b --- /dev/null +++ b/docs/hosting/deploy-quick-start.md @@ -0,0 +1,69 @@ +# Reflex Hosting Service + +```python exec +import reflex as rx +from pcweb import constants +from pcweb.pages import docs +from pcweb.templates.docpage import doccmdoutput +``` + +So far, we have been running our apps locally on our own machines. +But what if we want to share our apps with the world? This is where +the hosting service comes in. + +## Quick Start + +Reflex’s hosting service makes it easy to deploy your apps without worrying about configuring the infrastructure. + +### Prerequisites + +1. Hosting service requires `reflex>=0.3.2`. +2. This tutorial assumes you have successfully `reflex init` and `reflex run` your app. +3. Also make sure you have a `requirements.txt` file at the top level app directory that contains all your python dependencies! + +### Authentication + +First, create an account or log into it using the following command. + +```bash +reflex login +``` + +You will be redirected to your browser where you can authenticate through Github or Gmail. + +### Deployment + +Once you have successfully authenticated, you can start deploying your apps. + +Navigate to the project directory that you want to deploy and type the following command: + +```bash +reflex deploy +``` + +The command is by default interactive. It asks you a few questions for information required for the deployment. + +**Name**: choose a name for the deployed app. This name will be part of the deployed app URL, i.e. `.reflex.run`. The name should only contain domain name safe characters: no slashes, no underscores. Domain names are case insensitive. To avoid confusion, the name you choose here is also case insensitive. If you enter letters in upper cases, we automatically convert them to lower cases. + +**Regions**: enter the region code here or press `Enter` to accept the default. The default code `sjc` stands for San Jose, California in the US west coast. Check the list of supported regions at [reflex deployments regions](#reflex-deployments-regions). + +**Envs**: `Envs` are environment variables. You might not have used them at all in your app. In that case, press `Enter` to skip. More on the environment variables in the later section [Environment Variables](#environment-variables). + +That’s it! You should receive some feedback on the progress of your deployment and in a few minutes your app should be up. 🎉 + +```md alert info +Once your code is uploaded, the hosting service will start the deployment. After a complete upload, exiting from the command **does not** affect the deployment process. The command prints a message when you can safely close it without affecting the deployment. +``` + +## See it in Action + +Below is a video of deploying the [AI chat app]({docs.tutorial.intro.path}) to our hosting service. + +```python eval +rx.center( + rx.video(url="https://www.youtube.com/embed/pf3FKE26hx4"), + rx.box(height="3em"), + width="100%", + padding_y="2em" +) +``` diff --git a/docs/hosting/hosting-cli-commands.md b/docs/hosting/hosting-cli-commands.md new file mode 100644 index 000000000..0871462a1 --- /dev/null +++ b/docs/hosting/hosting-cli-commands.md @@ -0,0 +1,354 @@ +# Reflex Hosting Service CLI Commands + +```python exec +import reflex as rx +from pcweb import constants +from pcweb.templates.docpage import doccmdoutput +``` + +## Concepts + +### Requirements + +To be able to deploy your app, we ask that you prepare a `requirements.txt` file containing all the required Python packages for it. The hosting service runs a `pip install` command based on this file to prepare the instances that run your app. We recommend that you use a Python virtual environment when starting a new app, and only install the necessary packages. This reduces the preparation time installing no more packages than needed, and your app is deployed faster. There are a lot of resources online on Python virtual environment tools and how to capture the packages in a `requirements.txt` file. + +### Environment Variables + +When deploying to Reflex's hosting service, the command prompt asks if you want to add any environment variables. These are encrypted and safely stored. We recommend that backend API keys or secrets are entered as `envs`. Make sure to enter the `envs` without any quotation marks. + +The environment variables are key value pairs. We do not show the values of them in any CLI commands, only their names (or keys). However, if your app intentionally prints the values of these variables, the logs returned still contain the printed values. At the moment, the logs are not censored for anything resembling secrets. Only the app owner and Reflex team admins can access these logs. + +You access the values of `envs` by referencing `os.environ` with their names as keys in your app's backend. For example, if you set an env `ASYNC_DB_URL`, you are able to access it by `os.environ["ASYNC_DB_URL"]`. Some Python libraries automatically look for certain environment variables. For example, `OPENAI_API_KEY` for the `openai` python client. The `boto3` client credentials can be configured by setting `AWS_ACCESS_KEY_ID`,`AWS_SECRET_ACCESS_KEY`. This information is typically available in the documentation of the Python packages you use. + +### Updating Deployment + +To redeploy or update your app, navigate to the project directory and type `reflex deploy` again. This command communicates with the hosting service to automatically detects your existing app by the same name. This time the deploy command overwrites the app. You should see a prompt similar to `Overwrite deployment [ app-name ] ...`. This operation is a complete overwrite and not an incremental update. + +## CLI Command Reference + +All the `reflex` commands come with a help manual. The help manual lists additional command options that may be useful. You type `--help` to see the help manual. Some commands are organized under a `subcommands` series. Here is an example below. Note that the help manual may look different depending on the version of `reflex` or the `reflex-hosting-cli`. + +```python eval +doccmdoutput( + command="reflex deployments --help", + output="""Usage: reflex deployments [OPTIONS] COMMAND [ARGS]... + + Subcommands for managing the Deployments. + +Options: + --help Show this message and exit. + +Commands: + build-logs Get the build logs for a deployment. + delete Delete a hosted instance. + list List all the hosted deployments of the authenticated user. + logs Get the logs for a deployment. + regions List all the regions of the hosting service. + status Check the status of a deployment. +""" +) +``` + +### Authentication Commands + +#### reflex login + +When you type the `reflex login` command for the very first time, it opens the hosting service login page in your browser. We authenticate users through OAuth. At the moment the supported OAuth providers are Github and Gmail. You should be able to revoke such authorization on your Github and Google account settings page. We do not log into your Github or Gmail account. OAuth authorization provides us your email address and in case of Github your username handle. We use those to create an account for you. The email used in the original account creation is used to identify you as a user. If you have authenticated using different emails, those create separate accounts. To switch to another account, first log out using the `reflex logout` command. More details on the logout command are in [reflex logout](#reflex-logout) section. + +```python eval +doccmdoutput( + command="reflex login", + output="""Opening https://control-plane.reflex.run ... +Successfully logged in. +""", +) +``` + +After authentication, the browser redirects to the original hosting service login page. It shows that you have logged in. Now you can return to the terminal where you type the login command. It should print a message such as `Successfully logged in`. + +Your access token is cached locally in the reflex support directory. For subsequent login commands, the cached token is validated first. If the token is still valid, the CLI command simply shows `You’re already logged in`. If the token is expired or simply not valid for any reason, the login command tries to open your browser again for web based authentication. + +#### reflex logout + +When you successfully authenticate with the hosting service, there is information cached in two different places: a file containing the access token in the reflex support directory, and cookies in your browser. The cookies include the access token, a refresh token, some unix epochs indicating when the access token expires. The logout command removes the cached information from these places. + +### Deployment Commands + +#### reflex deploy + +This is the command to deploy a reflex app from its top level app directory. This directory contains a `rxconfig.py` where you run `reflex init` and `reflex run`. + +A `requirements.txt` file is required. The deploy command checks the content of this file against the top level packages installed in your current Python environment. If the command detects new packages in your Python environment, or newer versions of the same packages, it prints the difference and asks if you would like to update your `requirements.txt`. Make sure you double check the suggested updates. This functionality is added in more recent versions of the hosting CLI package `reflex-hosting-cli>=0.1.3`. + +```python eval +doccmdoutput( + command="reflex deploy", + output="""Info: The requirements.txt may need to be updated. +--- requirements.txt ++++ new_requirements.txt +@@ -1,3 +1,3 @@ +-reflex>=0.2.0 +-openai==0.28 ++openai==0.28.0 ++reflex==0.3.8 + +Would you like to update requirements.txt based on the changes above? [y/n]: y + +Choose a name for your deployed app (https://.reflex.run) +Enter to use default. (webui-gray-sun): demo-chat +Region to deploy to. See regions: https://bit.ly/46Qr3mF +Enter to use default. (sjc): lax +Environment variables for your production App ... + * env-1 name (enter to skip): OPENAI_API_KEY + env-1 value: sk-********************* + * env-2 name (enter to skip): +Finished adding envs. +──────────────── Compiling production app and preparing for export. ──────────────── +Zipping Backend: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 12/12 0:00:00 +Uploading Backend code and sending request ... +Backend deployment will start shortly. +──────────────── Compiling production app and preparing for export. ──────────────── +Compiling: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 9/9 0:00:00 +Creating Production Build: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 9/9 0:00:07 +Zipping Frontend: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 20/20 0:00:00 +Uploading Frontend code and sending request ... +Frontend deployment will start shortly. +───────────────────────────── Deploying production app. ──────────────────────────── +Deployment will start shortly: https://demo-chat.reflex.run +Closing this command now will not affect your deployment. +Waiting for server to report progress ... +2024-01-12 12:24:54.188271 PST | Updating frontend... +2024-01-12 12:24:55.074264 PST | Frontend updated! +2024-01-12 12:24:55.137679 PST | Deploy success (frontend) +2024-01-12 12:24:59.722384 PST | Updating backend... +2024-01-12 12:25:01.006386 PST | Building backend image... +2024-01-12 12:26:03.672379 PST | Deploying backend image... +2024-01-12 12:26:21.017946 PST | Backend updated! +2024-01-12 12:26:21.018003 PST | Deploy success (backend) +Waiting for the new deployment to come up +Your site [ demo-chat ] at ['lax'] is up: https://demo-chat.reflex.run +""", +) +``` + +The deploy command is by default interactive. To deploy without interaction, add `--no-interactive` and set the relevant command options as deployment settings. Type `reflex deploy --help` to see the help manual for explanations on each option. The deploy sequences are the same, whether the deploy command is interactive or not. + +```bash +reflex deploy --no-interactive -k todo -r sjc -r sea --env OPENAI_API_KEY=YOU-KEY-NO-EXTRA-QUOTES --env DB_URL=YOUR-EXTERNAL-DB-URI --env KEY3=THATS-ALOTOF-KEYS +``` + +#### reflex deployments list + +List all your deployments. + +```python eval +doccmdoutput( + command="reflex deployments list", + output="""key regions app_name reflex_version cpus memory_mb url envs +---------------------------- ------- -------------------- ---------------- ------- ----------- ------------------------------------------ --------- +webui-navy-star ['sjc'] webui 0.3.7 1 1024 https://webui-navy-star.reflex.run ['OPENAI_API_KEY'] +chatroom-teal-ocean ['ewr'] chatroom 0.3.2 1 1024 https://chatroom-teal-ocean.reflex.run [] +sales-navy-moon ['phx'] sales 0.3.4 1 1024 https://sales-navy-moon.reflex.run [] +simple-background-tasks ['yul'] lorem_stream 0.3.7 1 1024 https://simple-background-tasks.reflex.run [] +snakegame ['sjc'] snakegame 0.3.3 1 1024 https://snakegame.reflex.run [] +basic-crud-navy-apple ['dfw'] basic_crud 0.3.8 1 1024 https://basic-crud-navy-apple.reflex.run [] +""", +) +``` + +#### reflex deployments status `app-name` + +Get the status of a specific app, including backend and frontend. + +```python eval +doccmdoutput( + command="reflex deployments status clock-gray-piano", + output="""Getting status for [ clock-gray-piano ] ... + +backend_url reachable updated_at +----------------------------------------- ----------- ------------ +https://rxh-prod-clock-gray-piano.fly.dev False N/A + + +frontend_url reachable updated_at +----------------------------------------- ----------- ----------------------- +https://clock-gray-piano.reflex.run True 2023-10-13 15:23:07 PDT +""", +) +``` + +#### reflex deployments logs `app-name` + +Get the logs from a specific deployment. + +The returned logs are the messages printed to console. If you have `print` statements in your code, they show up in these logs. By default, the logs command return the latest 100 lines of logs and continue to stream any new lines. + +We have added more options to this command including `from` and `to` timestamps and the limit on how many lines of logs to fetch. Accepted timestamp formats include the ISO 8601 format, unix epoch and relative timestamp. A relative timestamp is some time units ago from `now`. The units are `d (day), h (hour), m (minute), s (second)`. For example, `--from 3d --to 4h` queries from 3 days ago up to 4 hours ago. For the exact syntax in the version of CLI you use, refer to the help manual. + +```python eval +doccmdoutput( + command="reflex deployments logs todo", + output="""Note: there is a few seconds delay for logs to be available. +2023-10-13 22:18:39.696028 | rxh-dev-todo | info | Pulling container image registry.fly.io/rxh-dev-todo:depot-1697235471 +2023-10-13 22:18:41.462929 | rxh-dev-todo | info | Pulling container image registry.fly.io/rxh-dev-todo@sha256:60b7b531e99e037f2fb496b3e05893ee28f93a454ee618bda89a531a547c4002 +2023-10-13 22:18:45.963840 | rxh-dev-todo | info | Successfully prepared image registry.fly.io/rxh-dev-todo@sha256:60b7b531e99e037f2fb496b3e05893ee28f93a454ee618bda89a531a547c4002 (4.500906837s) +2023-10-13 22:18:46.134860 | rxh-dev-todo | info | Successfully prepared image registry.fly.io/rxh-dev-todo:depot-1697235471 (6.438815793s) +2023-10-13 22:18:46.210583 | rxh-dev-todo | info | Configuring firecracker +2023-10-13 22:18:46.434645 | rxh-dev-todo | info | [ 0.042971] Spectre V2 : WARNING: Unprivileged eBPF is enabled with eIBRS on, data leaks possible via Spectre v2 BHB attacks! +2023-10-13 22:18:46.477693 | rxh-dev-todo | info | [ 0.054250] PCI: Fatal: No config space access function found +2023-10-13 22:18:46.664016 | rxh-dev-todo | info | Configuring firecracker +""", +) +``` + +#### reflex deployments build-logs `app-name` + +Get the logs of the hosting service deploying the app. + +```python eval +doccmdoutput( + command="reflex deployments build-logs webcam-demo", + output="""Note: there is a few seconds delay for logs to be available. +2024-01-08 11:02:46.109785 PST | fly-controller-prod | #8 extracting sha256:bd9ddc54bea929a22b334e73e026d4136e5b73f5cc29942896c72e4ece69b13d 0.0s done | None | None +2024-01-08 11:02:46.109811 PST | fly-controller-prod | #8 DONE 5.3s | None | None +2024-01-08 11:02:46.109834 PST | fly-controller-prod | | None | None +2024-01-08 11:02:46.109859 PST | fly-controller-prod | #8 [1/4] FROM public.ecr.aws/p3v4g4o2/reflex-hosting-base:v0.1.8-py3.11@sha256:9e8569507f349d78d41a86e1eb29a15ebc9dece487816875bbc874f69dcf7ecf | None | None +... +... +2024-01-08 11:02:50.913748 PST | fly-controller-prod | #11 [4/4] RUN . /home/reflexuser/venv/bin/activate && pip install --no-color --no-cache-dir -q -r /home/reflexuser/app/requirements.txt reflex==0.3.4 | None | None +... +... +2024-01-08 11:03:07.430922 PST | fly-controller-prod | #12 pushing layer sha256:d9212ef47485c9f363f105a05657936394354038a5ae5ce03c6025f7f8d2d425 3.5s done | None | None +2024-01-08 11:03:07.881471 PST | fly-controller-prod | #12 pushing layer sha256:ee46d14ae1959b0cacda828e5c4c1debe32c9c4c5139fb670cde66975a70c019 3.9s done | None | None +... +2024-01-08 11:03:13.943166 PST | fly-controller-prod | Built backend image | None | None +2024-01-08 11:03:13.943174 PST | fly-controller-prod | Deploying backend image... | None | None +2024-01-08 11:03:13.943311 PST | fly-controller-prod | Running sys_run | None | None +... +2024-01-08 11:03:31.005887 PST | fly-controller-prod | Checking for valid image digest to be deployed to machines... | None | None +2024-01-08 11:03:31.005893 PST | fly-controller-prod | Running sys_run | None | None +2024-01-08 11:03:32.411762 PST | fly-controller-prod | Backend updated! | None | None +2024-01-08 11:03:32.481276 PST | fly-controller-prod | Deploy success (backend) | None | None +""", +) +``` + +The hosting service prints log messages when preparing and deploying your app. These log messages are called build logs. Build logs are useful in troubleshooting deploy failures. For example, if there is a package `numpz==1.26.3` (supposed to be `numpy`) in the `requirements.txt`, hosting service will be unable to install it. That package does not exist. We expect to find a few lines in the build logs indicating that the `pip install` command fails. + +#### reflex deployments delete `app-name` + +Delete a specific deployment. + +### Public Commands + +These commands do not require authentication. + +#### reflex deployments regions + +List all the valid regions to select for a deployment. + +```python eval +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Region Code"), + rx.table.column_header_cell("Region"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("alt"), + rx.table.cell("Atlanta, Georgia (US)"), + ), + rx.table.row( + rx.table.row_header_cell("bog"), + rx.table.cell("Bogotá, Colombia"), + ), + rx.table.row( + rx.table.row_header_cell("bos"), + rx.table.cell("Boston, Massachusetts (US)"), + ), + rx.table.row( + rx.table.row_header_cell("cdg"), + rx.table.cell("Paris, France"), + ), + rx.table.row( + rx.table.row_header_cell("den"), + rx.table.cell("Denver, Colorado (US)"), + ), + rx.table.row( + rx.table.row_header_cell("dfw"), + rx.table.cell("Dallas, Texas (US)"), + ), + rx.table.row( + rx.table.row_header_cell("eze"), + rx.table.cell("Ezeiza, Argentina"), + ), + rx.table.row( + rx.table.row_header_cell("fra"), + rx.table.cell("Frankfurt, Germany"), + ), + rx.table.row( + rx.table.row_header_cell("hkg"), + rx.table.cell("Hong Kong, Hong Kong"), + ), + rx.table.row( + rx.table.row_header_cell("iad"), + rx.table.cell("Ashburn, Virginia (US)"), + ), + rx.table.row( + rx.table.row_header_cell("lax"), + rx.table.cell("Los Angeles, California (US)"), + ), + rx.table.row( + rx.table.row_header_cell("lhr"), + rx.table.cell("London, United Kingdom"), + ), + rx.table.row( + rx.table.row_header_cell("mad"), + rx.table.cell("Madrid, Spain"), + ), + rx.table.row( + rx.table.row_header_cell("mia"), + rx.table.cell("Miami, Florida (US)"), + ), + rx.table.row( + rx.table.row_header_cell("ord"), + rx.table.cell("Chicago, Illinois (US)"), + ), + rx.table.row( + rx.table.row_header_cell("scl"), + rx.table.cell("Santiago, Chile"), + ), + rx.table.row( + rx.table.row_header_cell("sea"), + rx.table.cell("Seattle, Washington (US)"), + ), + rx.table.row( + rx.table.row_header_cell("sin"), + rx.table.cell("Singapore, Singapore"), + ), + rx.table.row( + rx.table.row_header_cell("sjc"), + rx.table.cell("San Jose, California (US)"), + ), + rx.table.row( + rx.table.row_header_cell("syd"), + rx.table.cell("Sydney, Australia"), + ), + rx.table.row( + rx.table.row_header_cell("waw"), + rx.table.cell("Warsaw, Poland"), + ), + rx.table.row( + rx.table.row_header_cell("yul"), + rx.table.cell("Montréal, Canada"), + ), + rx.table.row( + rx.table.row_header_cell("yyz"), + rx.table.cell("Toronto, Canada"), + ), + ), + variant="surface", +) +``` diff --git a/docs/hosting/self-hosting.md b/docs/hosting/self-hosting.md new file mode 100644 index 000000000..40d303c37 --- /dev/null +++ b/docs/hosting/self-hosting.md @@ -0,0 +1,121 @@ +```python exec +import reflex as rx + +from pcweb.pages.docs import getting_started +``` + +# Self Hosting + +When available, we recommend using `reflex deploy`, but you can also host your +apps yourself in the meantime. + +Clone your code to a server and install the [requirements]({getting_started.installation.path}). + +## API URL + +Edit your `rxconfig.py` file and set `api_url` to the publicly accessible IP +address or hostname of your server, with the port `:8000` at the end. Setting +this correctly is essential for the frontend to interact with the backend state. + +For example if your server is at `app.example.com`, your config would look like this: + +```python +config = rx.Config( + app_name="your_app_name", + api_url="http://app.example.com:8000", +) +``` + +It is also possible to set the environment variable `API_URL` at run time or +export time to retain the default for local development. + +## Production Mode + +Then run your app in production mode: + +```bash +reflex run --env prod +``` + +Production mode creates an optimized build of your app. By default, the static +frontend of the app (HTML, Javascript, CSS) will be exposed on port `3000` and +the backend (event handlers) will be listening on port `8000`. + +```md alert warning +# Reverse Proxy and Websockets +Because the backend uses websockets, some reverse proxy servers, like [nginx](https://nginx.org/en/docs/http/websocket.html) or [apache](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#protoupgrade), must be configured to pass the `Upgrade` header to allow backend connectivity. +``` + +## Exporting a Static Build + +Exporting a static build of the frontend allows the app to be served using a +static hosting provider, like Netlify or Github Pages. Be sure `api_url` is set +to an accessible backend URL when the frontend is exported. + +```bash +API_URL=http://app.example.com:8000 reflex export +``` + +This will create a `frontend.zip` file with your app's minified HTML, +Javascript, and CSS build that can be uploaded to your static hosting service. + +It also creates a `backend.zip` file with your app's backend python code to +upload to your server and run. + +You can export only the frontend or backend by passing in the `--frontend-only` +or `--backend-only` flags. + +It is also possible to export the components without zipping. To do +this, use the `--no-zip` parameter. This provides the frontend in the +`.web/_static/` directory and the backend can be found in the root directory of +the project. + +## Reflex Container Service + +Another option is to run your Reflex service in a container. For this +purpose, a `Dockerfile` and additional documentation is available in the Reflex +project in the directory `docker-example`. + +For the build of the container image it is necessary to edit the `rxconfig.py` +and the add the `requirements.txt` +to your project folder. The following changes are necessary in `rxconfig.py`: + +```python +config = rx.Config( + app_name="app", + api_url="http://app.example.com:8000", +) +``` + +Notice that the `api_url` should be set to the externally accessible hostname or +IP, as the client browser must be able to connect to it directly to establish +interactivity. + +You can find the `requirements.txt` in the `docker-example` folder of the +project too. + +The project structure should looks like this: + +```bash +hello +├── .web +├── assets +├── hello +│ ├── __init__.py +│ └── hello.py +├── rxconfig.py +├── Dockerfile +└── requirements.txt +``` + +After all changes have been made, the container image can now be created as follows. + +```bash +docker build -t reflex-project:latest . +``` + +Finally, you can start your Reflex container service as follows. + +```bash +docker run -d -p 3000:3000 -p 8000:8000 --name app reflex-project:latest +``` diff --git a/docs/library/chakra/datadisplay/badge.md b/docs/library/chakra/datadisplay/badge.md new file mode 100644 index 000000000..8a8a0f2a8 --- /dev/null +++ b/docs/library/chakra/datadisplay/badge.md @@ -0,0 +1,44 @@ +--- +components: + - rx.chakra.Badge +--- + +```python exec +import reflex as rx +``` + +# Badge + +Badges are used to highlight an item's status for quick recognition. + +There are 3 variants of badges: `solid`, `subtle`, and `outline`. + +```python demo +rx.chakra.hstack( + rx.chakra.badge("Example", variant="solid", color_scheme="green"), + rx.chakra.badge("Example", variant="subtle", color_scheme="green"), + rx.chakra.badge("Example", variant="outline", color_scheme="green"), +) +``` + +Color schemes are an easy way to change the color of a badge. + +```python demo +rx.chakra.hstack( + rx.chakra.badge("Example", variant="subtle", color_scheme="green"), + rx.chakra.badge("Example", variant="subtle", color_scheme="red"), + rx.chakra.badge("Example", variant="subtle", color_scheme="yellow"), +) +``` + +You can also customize the badge through traditional style args. + +```python demo +rx.chakra.badge( + "Custom Badge", + bg="#90EE90", + color="#3B7A57", + border_color="#29AB87", + border_width=2 +) +``` diff --git a/docs/library/chakra/datadisplay/divider.md b/docs/library/chakra/datadisplay/divider.md new file mode 100644 index 000000000..8fb0404c1 --- /dev/null +++ b/docs/library/chakra/datadisplay/divider.md @@ -0,0 +1,34 @@ +--- +components: + - rx.chakra.Divider +--- + +```python exec +import reflex as rx +``` + +# Divider + +Dividers are a quick built in way to separate sections of content. + +```python demo +rx.chakra.vstack( + rx.chakra.text("Example"), + rx.chakra.divider(border_color="black"), + rx.chakra.text("Example"), + rx.chakra.divider(variant="dashed", border_color="black"), + width="100%", +) +``` + +If the vertical orientation is used, make sure that the parent component is assigned a height. + +```python demo +rx.chakra.center( + rx.chakra.divider( + orientation="vertical", + border_color = "black" + ), + height = "4em" +) +``` diff --git a/docs/library/chakra/datadisplay/list.md b/docs/library/chakra/datadisplay/list.md new file mode 100644 index 000000000..dc12a66c9 --- /dev/null +++ b/docs/library/chakra/datadisplay/list.md @@ -0,0 +1,68 @@ +--- +components: + - rx.chakra.List + - rx.chakra.ListItem + - rx.chakra.UnorderedList + - rx.chakra.OrderedList +--- + +```python exec +import reflex as rx +``` + +# List + +There are three types of lists: regular lists, ordered, unordered. + +The shorthand syntax used to create a list is by passing in a list of items. +These items can be components or Python primitives. + +```python demo +rx.chakra.list( + items=["Example 1", "Example 2", "Example 3"], + spacing=".25em" +) +``` + +The examples below have the explicit syntax of list and list_items. +Regular lists are used to display a list of items. +They have no bullet points or numbers and stack the list items vertically. + +```python demo +rx.chakra.list( + rx.chakra.list_item("Example 1"), + rx.chakra.list_item("Example 2"), + rx.chakra.list_item("Example 3"), +) +``` + +Unordered have bullet points to display the list items. + +```python demo +rx.chakra.unordered_list( + rx.chakra.list_item("Example 1"), + rx.chakra.list_item("Example 2"), + rx.chakra.list_item("Example 3"), +) +``` + +Ordered lists have numbers to display the list items. + +```python demo +rx.chakra.ordered_list( + rx.chakra.list_item("Example 1"), + rx.chakra.list_item("Example 2"), + rx.chakra.list_item("Example 3"), +) +``` + +Lists can also be used with icons. + +```python demo +rx.chakra.list( + rx.chakra.list_item(rx.chakra.icon(tag="check_circle", color = "green"), "Allowed"), + rx.chakra.list_item(rx.chakra.icon(tag="not_allowed", color = "red"), "Not"), + rx.chakra.list_item(rx.chakra.icon(tag="settings", color = "grey"), "Settings"), + spacing = ".25em" +) +``` diff --git a/docs/library/chakra/datadisplay/stat.md b/docs/library/chakra/datadisplay/stat.md new file mode 100644 index 000000000..7e85e978c --- /dev/null +++ b/docs/library/chakra/datadisplay/stat.md @@ -0,0 +1,41 @@ +--- +components: + - rx.chakra.Stat + - rx.chakra.StatLabel + - rx.chakra.StatNumber + - rx.chakra.StatHelpText + - rx.chakra.StatArrow + - rx.chakra.StatGroup +--- + +```python exec +import reflex as rx +``` + +# Stat + +The stat component is a great way to visualize statistics in a clean and concise way. + +```python demo +rx.chakra.stat( + rx.chakra.stat_label("Example Price"), + rx.chakra.stat_number("$25"), + rx.chakra.stat_help_text("The price of the item."), +) +``` + +Example of a stats in a group with arrow. + +```python demo +rx.chakra.stat_group( + rx.chakra.stat( + rx.chakra.stat_number("$250"), + rx.chakra.stat_help_text("%50", rx.chakra.stat_arrow(type_="increase")), + ), + rx.chakra.stat( + rx.chakra.stat_number("£100"), + rx.chakra.stat_help_text("%50", rx.chakra.stat_arrow(type_="decrease")), + ), + width="100%", +) +``` diff --git a/docs/library/chakra/datadisplay/table.md b/docs/library/chakra/datadisplay/table.md new file mode 100644 index 000000000..1c44de59a --- /dev/null +++ b/docs/library/chakra/datadisplay/table.md @@ -0,0 +1,142 @@ +--- +components: + - rx.chakra.Table + - rx.chakra.TableCaption + - rx.chakra.Thead + - rx.chakra.Tbody + - rx.chakra.Tfoot + - rx.chakra.Tr + - rx.chakra.Th + - rx.chakra.Td + - rx.chakra.TableContainer +--- + +```python exec +import reflex as rx +``` + +# Table + +Tables are used to organize and display data efficiently. +The table component differs from the `data_table`` component in that it is not meant to display large amounts of data. +It is meant to display data in a more organized way. + +Tables can be created with a shorthand syntax or by explicitly creating the table components. +The shorthand syntax is great for simple tables, but if you need more control over the table you can use the explicit syntax. + +Let's start with the shorthand syntax. +The shorthand syntax has `headers`, `rows`, and `footers` props. + +```python demo +rx.chakra.table_container( + rx.chakra.table( + headers=["Name", "Age", "Location"], + rows=[ + ("John", 30, "New York"), + ("Jane", 31, "San Francisco"), + ("Joe", 32, "Los Angeles") + ], + footers=["Footer 1", "Footer 2", "Footer 3"], + variant='striped' + ) +) +``` + +Let's create a simple table explicitly. In this example we will make a table with 2 columns: `Name` and `Age`. + +```python demo +rx.chakra.table( + rx.chakra.thead( + rx.chakra.tr( + rx.chakra.th("Name"), + rx.chakra.th("Age"), + ) + ), + rx.chakra.tbody( + rx.chakra.tr( + rx.chakra.td("John"), + rx.chakra.td(30), + ) + ), +) +``` + +In the examples we will be using this data to display in a table. + +```python exec +columns = ["Name", "Age", "Location"] +data = [ + ["John", 30, "New York"], + ["Jane", 25, "San Francisco"], +] +footer = ["Footer 1", "Footer 2", "Footer 3"] +``` + +```python +columns = ["Name", "Age", "Location"] +data = [ + ["John", 30, "New York"], + ["Jane", 25, "San Francisco"], +] +footer = ["Footer 1", "Footer 2", "Footer 3"] +``` + +Now lets create a table with the data we created. + +```python eval +rx.chakra.center( + rx.chakra.table_container( + rx.chakra.table( + rx.chakra.table_caption("Example Table"), + rx.chakra.thead( + rx.chakra.tr( + *[rx.chakra.th(column) for column in columns] + ) + ), + rx.chakra.tbody( + *[rx.chakra.tr(*[rx.chakra.td(item) for item in row]) for row in data] + ), + rx.chakra.tfoot( + rx.chakra.tr( + *[rx.chakra.th(item) for item in footer] + ) + ), + ) + ) +) +``` + +Tables can also be styled with the variant and color_scheme arguments. + +```python demo +rx.chakra.table_container( + rx.chakra.table( + rx.chakra.thead( + rx.chakra.tr( + rx.chakra.th("Name"), + rx.chakra.th("Age"), + rx.chakra.th("Location"), + ) + ), + rx.chakra.tbody( + rx.chakra.tr( + rx.chakra.td("John"), + rx.chakra.td(30), + rx.chakra.td("New York"), + ), + rx.chakra.tr( + rx.chakra.td("Jane"), + rx.chakra.td(31), + rx.chakra.td("San Francisco"), + ), + rx.chakra.tr( + rx.chakra.td("Joe"), + rx.chakra.td(32), + rx.chakra.td("Los Angeles"), + ) + ), + variant='striped', + color_scheme='teal' + ) +) +``` diff --git a/docs/library/chakra/disclosure/accordion.md b/docs/library/chakra/disclosure/accordion.md new file mode 100644 index 000000000..a70e6efed --- /dev/null +++ b/docs/library/chakra/disclosure/accordion.md @@ -0,0 +1,103 @@ +--- +components: + - rx.chakra.Accordion + - rx.chakra.AccordionItem + - rx.chakra.AccordionButton + - rx.chakra.AccordionPanel + - rx.chakra.AccordionIcon +--- + +```python exec +import reflex as rx +``` + +# Accordion + +Accordions allow you to hide and show content in a container under a header. + +Accordion consist of an outer accordion component and inner accordion items. +Each item has a optional button and panel. The button is used to toggle the panel's visibility. + +```python demo +rx.chakra.accordion( + rx.chakra.accordion_item( + rx.chakra.accordion_button( + rx.chakra.heading("Example"), + rx.chakra.accordion_icon(), + ), + rx.chakra.accordion_panel( + rx.chakra.text("This is an example of an accordion component.") + ) + ), + allow_toggle = True, + width = "100%" +) +``` + +An accordion can have multiple items. + +```python demo +rx.chakra.accordion( + rx.chakra.accordion_item( + rx.chakra.accordion_button( + rx.chakra.heading("Example 1"), + rx.chakra.accordion_icon(), + ), + rx.chakra.accordion_panel( + rx.chakra.text("This is an example of an accordion component.") + ), + ), + rx.chakra.accordion_item( + rx.chakra.accordion_button( + rx.chakra.heading("Example 2"), + rx.chakra.accordion_icon(), + ), + rx.chakra.accordion_panel( + rx.chakra.text("This is an example of an accordion component.") + ), + ), + allow_multiple = True, + bg="black", + color="white", + width = "100%" +) +``` + +You can create multilevel accordions by nesting accordions within the outer accordion panel. + +```python demo +rx.chakra.accordion( + rx.chakra.accordion_item( + rx.chakra.accordion_button( + rx.chakra.accordion_icon(), + rx.chakra.heading("Outer"), + + ), + rx.chakra.accordion_panel( + rx.chakra.accordion( + rx.chakra.accordion_item( + rx.chakra.accordion_button( + rx.chakra.accordion_icon(), + rx.chakra.heading("Inner"), + ), + rx.chakra.accordion_panel( + rx.chakra.badge("Inner Panel", variant="solid", color_scheme="green"), + ) + ) + ), + ) + ), + width = "100%" +) +``` + +You can also create an accordion using the shorthand syntax. +Pass a list of tuples to the `items` prop. +Each tuple should contain a label and a panel. + +```python demo +rx.chakra.accordion( + items=[("Label 1", rx.chakra.center("Panel 1")), ("Label 2", rx.chakra.center("Panel 2"))], + width="100%" +) +``` diff --git a/docs/library/chakra/disclosure/tabs.md b/docs/library/chakra/disclosure/tabs.md new file mode 100644 index 000000000..103844656 --- /dev/null +++ b/docs/library/chakra/disclosure/tabs.md @@ -0,0 +1,48 @@ +--- +components: + - rx.chakra.Tabs + - rx.chakra.TabList + - rx.chakra.Tab + - rx.chakra.TabPanel + - rx.chakra.TabPanels +--- + +```python exec +import reflex as rx +``` + +# Tabs + +Tab components allow you display content in multiple pages within a container. +These page are organized by a tab list and the corresponding tab panel can take in children components if needed. + +```python demo +rx.chakra.tabs( + rx.chakra.tab_list( + rx.chakra.tab("Tab 1"), + rx.chakra.tab("Tab 2"), + rx.chakra.tab("Tab 3"), + ), + rx.chakra.tab_panels( + rx.chakra.tab_panel(rx.chakra.text("Text from tab 1.")), + rx.chakra.tab_panel(rx.chakra.checkbox("Text from tab 2.")), + rx.chakra.tab_panel(rx.chakra.button("Text from tab 3.", color="black")), + ), + bg="black", + color="white", + shadow="lg", +) +``` + +You can create a tab component using the shorthand syntax. +Pass a list of tuples to the `items` prop. +Each tuple should contain a label and a panel. + +```python demo +rx.chakra.tabs( + items = [("Tab 1", rx.chakra.text("Text from tab 1.")), ("Tab 2", rx.chakra.checkbox("Text from tab 2.")), ("Tab 3", rx.chakra.button("Text from tab 3.", color="black"))], + bg="black", + color="white", + shadow="lg", +) +``` diff --git a/docs/library/chakra/feedback/alert.md b/docs/library/chakra/feedback/alert.md new file mode 100644 index 000000000..b293dbc87 --- /dev/null +++ b/docs/library/chakra/feedback/alert.md @@ -0,0 +1,70 @@ +--- +components: + - rx.chakra.Alert + - rx.chakra.AlertIcon + - rx.chakra.AlertTitle + - rx.chakra.AlertDescription +--- + +```python exec +import reflex as rx +``` + +# Alert + +Alerts are used to communicate a state that affects a system, feature or page. +An example of the different alert statuses is shown below. + +```python demo +rx.chakra.vstack( + rx.chakra.alert( + rx.chakra.alert_icon(), + rx.chakra.alert_title("Error Reflex version is out of date."), + status="error", + ), + rx.chakra.alert( + rx.chakra.alert_icon(), + rx.chakra.alert_title("Warning Reflex version is out of date."), + status="warning", + ), + rx.chakra.alert( + rx.chakra.alert_icon(), + rx.chakra.alert_title("Reflex version is up to date."), + status="success", + ), + rx.chakra.alert( + rx.chakra.alert_icon(), + rx.chakra.alert_title("Reflex version is 0.1.32."), + status="info", + ), + width="100%", +) +``` + +Along with different status types, alerts can also have different style variants and an optional description. +By default the variant is 'subtle'. + +```python demo +rx.chakra.vstack( + rx.chakra.alert( + rx.chakra.alert_icon(), + rx.chakra.alert_title("Reflex version is up to date."), + rx.chakra.alert_description("No need to update."), + status="success", + variant="subtle", + ), + rx.chakra.alert( + rx.chakra.alert_icon(), + rx.chakra.alert_title("Reflex version is up to date."), + status="success", + variant="solid", + ), + rx.chakra.alert( + rx.chakra.alert_icon(), + rx.chakra.alert_title("Reflex version is up to date."), + status="success", + variant="top-accent", + ), + width="100%", +) +``` diff --git a/docs/library/chakra/feedback/circularprogress.md b/docs/library/chakra/feedback/circularprogress.md new file mode 100644 index 000000000..ca6f56326 --- /dev/null +++ b/docs/library/chakra/feedback/circularprogress.md @@ -0,0 +1,26 @@ +--- +components: + - rx.chakra.CircularProgress + - rx.chakra.CircularProgressLabel +--- + +```python exec +import reflex as rx +``` + +# CircularProgress + +The CircularProgress component is used to indicate the progress for determinate and indeterminate processes. +Determinate progress: fills the circular track with color, as the indicator moves from 0 to 360 degrees. +Indeterminate progress: grows and shrinks the indicator while moving along the circular track. + +```python demo +rx.chakra.hstack( + rx.chakra.circular_progress(value=0), + rx.chakra.circular_progress(value=25), + rx.chakra.circular_progress(rx.chakra.circular_progress_label(50), value=50), + rx.chakra.circular_progress(value=75), + rx.chakra.circular_progress(value=100), + rx.chakra.circular_progress(is_indeterminate=True), +) +``` diff --git a/docs/library/chakra/feedback/progress.md b/docs/library/chakra/feedback/progress.md new file mode 100644 index 000000000..4aa42f88d --- /dev/null +++ b/docs/library/chakra/feedback/progress.md @@ -0,0 +1,24 @@ +--- +components: + - rx.chakra.Progress +--- + +```python exec +import reflex as rx +``` + +# Progress + +Progress is used to display the progress status for a task that takes a long time or consists of several steps. + +```python demo +rx.chakra.vstack( + rx.chakra.progress(value=0, width="100%"), + rx.chakra.progress(value=50, width="100%"), + rx.chakra.progress(value=75, width="100%"), + rx.chakra.progress(value=100, width="100%"), + rx.chakra.progress(is_indeterminate=True, width="100%"), + spacing="1em", + min_width=["10em", "20em"], +) +``` diff --git a/docs/library/chakra/feedback/skeleton.md b/docs/library/chakra/feedback/skeleton.md new file mode 100644 index 000000000..46f1d4e3f --- /dev/null +++ b/docs/library/chakra/feedback/skeleton.md @@ -0,0 +1,54 @@ +--- +components: + - rx.chakra.Skeleton + - rx.chakra.SkeletonCircle + - rx.chakra.SkeletonText +--- + +```python exec +import reflex as rx +``` + +# Skeleton + +Skeleton is used to display the loading state of some components. + +```python demo +rx.chakra.stack( + rx.chakra.skeleton(height="10px", speed=1.5), + rx.chakra.skeleton(height="15px", speed=1.5), + rx.chakra.skeleton(height="20px", speed=1.5), + width="50%", +) +``` + +Along with the basic skeleton box there are also a skeleton circle and text for ease of use. + +```python demo +rx.chakra.stack( + rx.chakra.skeleton_circle(size="30px"), + rx.chakra.skeleton_text(no_of_lines=8), + width="50%", +) +``` + +Another feature of skeleton is the ability to animate colors. +We provide the args start_color and end_color to animate the color of the skeleton component(s). + +```python demo +rx.chakra.stack( + rx.chakra.skeleton_text( + no_of_lines=5, start_color="pink.500", end_color="orange.500" + ), + width="50%", +) +``` + +You can prevent the skeleton from loading by using the `is_loaded` prop. + +```python demo +rx.chakra.vstack( + rx.chakra.skeleton(rx.chakra.text("Text is already loaded."), is_loaded=True), + rx.chakra.skeleton(rx.chakra.text("Text is already loaded."), is_loaded=False), +) +``` diff --git a/docs/library/chakra/feedback/spinner.md b/docs/library/chakra/feedback/spinner.md new file mode 100644 index 000000000..79602f6fc --- /dev/null +++ b/docs/library/chakra/feedback/spinner.md @@ -0,0 +1,38 @@ +--- +components: + - rx.chakra.Spinner +--- + +```python exec +import reflex as rx +``` + +# Spinner + +Spinners provide a visual cue that an event is either processing, awaiting a course of change or a result. + +```python demo +rx.chakra.hstack( + rx.chakra.spinner(color="red", size="xs"), + rx.chakra.spinner(color="orange", size="sm"), + rx.chakra.spinner(color="green", size="md"), + rx.chakra.spinner(color="blue", size="lg"), + rx.chakra.spinner(color="purple", size="xl"), +) +``` + +Along with the color you can style further with props such as speed, empty color, and thickness. + +```python demo +rx.chakra.hstack( + rx.chakra.spinner(color="lightgreen", thickness=1, speed="1s", size="xl"), + rx.chakra.spinner(color="lightgreen", thickness=5, speed="1.5s", size="xl"), + rx.chakra.spinner( + color="lightgreen", + thickness=10, + speed="2s", + empty_color="red", + size="xl", + ), +) +``` diff --git a/docs/library/chakra/forms/button.md b/docs/library/chakra/forms/button.md new file mode 100644 index 000000000..cfa117b1c --- /dev/null +++ b/docs/library/chakra/forms/button.md @@ -0,0 +1,375 @@ +--- +components: + - rx.chakra.Button + - rx.chakra.IconButton +--- + +```python exec +import reflex as rx +from pcweb.templates.docpage import docdemo + + +basic_button = """rx.chakra.button("Click Me!") +""" +button_style = """rx.chakra.button_group( + rx.chakra.button("Example", bg="lightblue", color="black", size = 'sm'), + rx.chakra.button("Example", bg="orange", color="white", size = 'md'), + rx.chakra.button("Example", color_scheme="red", size = 'lg'), + space = "1em", +) +""" +button_visual_states = """rx.chakra.button_group( + rx.chakra.button("Example", bg="lightgreen", color="black", is_loading = True), + rx.chakra.button("Example", bg="lightgreen", color="black", is_disabled = True), + rx.chakra.button("Example", bg="lightgreen", color="black", is_active = True), + space = '1em', +) +""" + +button_group_example = """rx.chakra.button_group( + rx.chakra.button(rx.chakra.icon(tag="minus"), color_scheme="red"), + rx.chakra.button(rx.chakra.icon(tag="add"), color_scheme="green"), + is_attached=True, +) +""" + +button_state = """class ButtonState(rx.State): + count: int = 0 + + def increment(self): + self.count += 1 + + def decrement(self): + self.count -= 1 +""" +exec(button_state) +button_state_example = """rx.chakra.hstack( + rx.chakra.button( + "Decrement", + bg="#fef2f2", + color="#b91c1c", + border_radius="lg", + on_click=ButtonState.decrement, + ), + rx.chakra.heading(ButtonState.count, font_size="2em", padding_x="0.5em"), + rx.chakra.button( + "Increment", + bg="#ecfdf5", + color="#047857", + border_radius="lg", + on_click=ButtonState.increment, + ), +) +""" + + +button_state_code = f""" +import reflex as rx + +{button_state} + +def index(): + return {button_state_example} + +app = rx.chakra.App() +app.add_page(index)""" + +button_state2 = """class ExampleButtonState(rx.State): + text_value: str = "Random value" +""" +exec(button_state2) + +button_state2_render_code = """rx.chakra.vstack( + rx.chakra.text(ExampleButtonState.text_value), + rx.chakra.button( + "Change Value", + on_click=ExampleButtonState.set_text_value("Modified value")) + ) +""" + +button_state2_code = f""" +import reflex as rx + +{button_state2} + +def index(): + return {button_state2_render_code} + +app = rx.chakra.App() +app.add_page(index)""" + + +button_sizes = ( +"""rx.chakra.button_group( + rx.chakra.button( + 'Example', bg='lightblue', color='black', size='sm' + ), + rx.chakra.button( + 'Example', bg='orange', color='white', size='md' + ), + rx.chakra.button('Example', color_scheme='red', size='lg'), +) +""" +) + +button_colors = ( +"""rx.chakra.button_group( + rx.chakra.button('White Alpha', color_scheme='whiteAlpha', min_width='unset'), + rx.chakra.button('Black Alpha', color_scheme='blackAlpha', min_width='unset'), + rx.chakra.button('Gray', color_scheme='gray', min_width='unset'), + rx.chakra.button('Red', color_scheme='red', min_width='unset'), + rx.chakra.button('Orange', color_scheme='orange', min_width='unset'), + rx.chakra.button('Yellow', color_scheme='yellow', min_width='unset'), + rx.chakra.button('Green', color_scheme='green', min_width='unset'), + rx.chakra.button('Teal', color_scheme='teal', min_width='unset'), + rx.chakra.button('Blue', color_scheme='blue', min_width='unset'), + rx.chakra.button('Cyan', color_scheme='cyan', min_width='unset'), + rx.chakra.button('Purple', color_scheme='purple', min_width='unset'), + rx.chakra.button('Pink', color_scheme='pink', min_width='unset'), + rx.chakra.button('LinkedIn', color_scheme='linkedin', min_width='unset'), + rx.chakra.button('Facebook', color_scheme='facebook', min_width='unset'), + rx.chakra.button('Messenger', color_scheme='messenger', min_width='unset'), + rx.chakra.button('WhatsApp', color_scheme='whatsapp', min_width='unset'), + rx.chakra.button('Twitter', color_scheme='twitter', min_width='unset'), + rx.chakra.button('Telegram', color_scheme='telegram', min_width='unset'), + width='100%', +) + +""" +) + +button_colors_render_code = ( +"""rx.chakra.button_group( + rx.chakra.button('White Alpha', color_scheme='whiteAlpha'), + rx.chakra.button('Black Alpha', color_scheme='blackAlpha'), + rx.chakra.button('Gray', color_scheme='gray'), + rx.chakra.button('Red', color_scheme='red'), + rx.chakra.button('Orange', color_scheme='orange'), + rx.chakra.button('Yellow', color_scheme='yellow'), + rx.chakra.button('Green', color_scheme='green'), + rx.chakra.button('Teal', color_scheme='teal'), + rx.chakra.button('Blue', color_scheme='blue'), + rx.chakra.button('Cyan', color_scheme='cyan'), + rx.chakra.button('Purple', color_scheme='purple'), + rx.chakra.button('Pink', color_scheme='pink'), + rx.chakra.button('LinkedIn', color_scheme='linkedin'), + rx.chakra.button('Facebook', color_scheme='facebook'), + rx.chakra.button('Messenger', color_scheme='messenger'), + rx.chakra.button('WhatsApp', color_scheme='whatsapp'), + rx.chakra.button('Twitter', color_scheme='twitter'), + rx.chakra.button('Telegram', color_scheme='telegram'), +) + +""" +) + +button_variants = ( +"""rx.chakra.button_group( + rx.chakra.button('Ghost Button', variant='ghost'), + rx.chakra.button('Outline Button', variant='outline'), + rx.chakra.button('Solid Button', variant='solid'), + rx.chakra.button('Link Button', variant='link'), + rx.chakra.button('Unstyled Button', variant='unstyled'), + ) +""" + +) + +button_disable = ( +"""rx.chakra.button('Inactive button', is_disabled=True)""" +) + +loading_states = ( +"""rx.chakra.button( + 'Random button', + is_loading=True, + loading_text='Loading...', + spinner_placement='start' + ) +""" +) + +stack_buttons_vertical = ( +"""rx.chakra.stack( + rx.chakra.button('Button 1'), + rx.chakra.button('Button 2'), + rx.chakra.button('Button 3'), + direction='column', +) + +""" +) + +stack_buttons_horizontal = ( +"""rx.chakra.stack( + rx.chakra.button('Button 1'), + rx.chakra.button('Button 2'), + rx.chakra.button('Button 3'), + direction='row', +) +""" +) + +button_group = ( +"""rx.chakra.button_group( + rx.chakra.button('Option 1'), + rx.chakra.button('Option 2'), + rx.chakra.button('Option 3'), + variant='outline', + is_attached=True, + ) +""" + +) + +``` + +# Button + +Buttons are essential elements in your application's user interface that users can click to trigger events. +This documentation will help you understand how to use button components effectively in your Reflex application. + +## Basic Usage + +A basic button component is created using the `rx.chakra.button` method: + +```python eval +docdemo(basic_button) +``` + +## Button Sizing + +You can change the size of a button by setting the size prop to one of the following +values: `xs`,`sm`,`md`, or `lg`. + +```python eval +docdemo(button_sizes) +``` + +## Button colors + +Customize the appearance of buttons by adjusting their color scheme through the color_scheme prop. +You have the flexibility to choose from a range of color scales provided by your design +system, such as whiteAlpha, blackAlpha, gray, red, blue, or even utilize your own custom color scale. + +```python demo box +eval(button_colors) +``` + +```python +{button_colors_render_code.strip()} +``` + +## Button Variants + +You can customize the visual style of your buttons using the variant prop. Here are the available button variants: + +- `ghost`: A button with a transparent background and visible text. +- `outline`: A button with no background color but with a border. +- `solid`: The default button style with a solid background color. +- `link`: A button that resembles a text link. +- `unstyled`: A button with no specific styling. + +```python eval +docdemo(button_variants) +``` + +## Disabling Buttons + +Make buttons inactive by setting the `is_disabled` prop to `True`. + +```python eval +docdemo(button_disable) +``` + +## Handling Loading States + +To indicate a loading state for a button after it's clicked, you can use the following properties: + +- `is_loading`: Set this property to `True` to display a loading spinner. +- `loading_text`: Optionally, you can provide loading text to display alongside the spinner. +- `spinner_placement`: You can specify the placement of the spinner element, which is 'start' by default. + +```python eval +docdemo(loading_states) +``` + +## Handling Click Events + +You can define what happens when a button is clicked using the `on_click` event argument. +For example, to change a value in your application state when a button is clicked: + +```python demo box +eval(button_state2_render_code) +``` + +```python +{button_state2_code.strip()} +``` + +In the code above, The value of `text_value` is changed through the `set_text_value` event handler upon clicking the button. +Reflex provides a default setter event_handler for every base var which can be accessed by prefixing the base var with the `set_` keyword. + +Here’s another example that creates two buttons to increase and decrease a count value: + +```python demo box +eval(button_state_example) +``` + +```python +{button_state_code.strip()} +``` + +In this example, we have a `ButtonState` state class that maintains a count base var. +When the "Increment" button is clicked, it triggers the `ButtonState.increment` event handler, and when the "Decrement" +button is clicked, it triggers the `ButtonState.decrement` event handler. + +## Special Events and Server-Side Actions + +Buttons in Reflex can trigger special events and server-side actions, +allowing you to create dynamic and interactive user experiences. +You can bind these events to buttons using the `on_click` prop. +For a comprehensive list of +available special events and server-side actions, please refer to the +[Special Events Documentation](/docs/api-reference/special-events) for detailed information and usage examples. + +## Grouping Buttons + +In your Reflex application, you can group buttons effectively using the `Stack` component and +the `ButtonGroup` component. Each of these options offers unique capabilities to help you structure +and style your buttons. + +## Using the `Stack` Component + +The `Stack` component allows you to stack buttons both vertically and horizontally, providing a flexible +layout for your button arrangements. + +## Stack Buttons Vertically + +```python eval +docdemo(stack_buttons_vertical) +``` + +## Stack Buttons Horizontally + +```python eval +docdemo(stack_buttons_horizontal) +``` + +With the `stack` component, you can easily create both vertical and horizontal button arrangements. + +## Using the `rx.chakra.button_group` Component + +The `ButtonGroup` component is designed specifically for grouping buttons. It allows you to: + +- Set the `size` and `variant` of all buttons within it. +- Add `spacing` between the buttons. +- Flush the buttons together by removing the border radius of their children as needed. + +```python eval +docdemo(button_group) +``` + +```md alert +# The `ButtonGroup` component stacks buttons horizontally, whereas the `Stack` component allows stacking buttons both vertically and horizontally. +``` diff --git a/docs/library/chakra/forms/buttongroup.md b/docs/library/chakra/forms/buttongroup.md new file mode 100644 index 000000000..e3fdb0610 --- /dev/null +++ b/docs/library/chakra/forms/buttongroup.md @@ -0,0 +1,150 @@ +--- +components: + - rx.chakra.ButtonGroup +--- + +```python exec +import reflex as rx +from pcweb.templates.docpage import docdemo + + +basic_button_group = ( +"""rx.chakra.button_group( + rx.chakra.button('Option 1'), + rx.chakra.button('Option 2'), + rx.chakra.button('Option 3'), + ) +""" +) + +button_group_attached = ( +"""rx.chakra.button_group( + rx.chakra.button('Option 1'), + rx.chakra.button('Option 2'), + rx.chakra.button('Option 3'), + is_attached=True, + ) + +""" +) + +button_group_variant = ( +"""rx.chakra.button_group( + rx.chakra.button('Option 1'), + rx.chakra.button('Option 2'), + rx.chakra.button('Option 3'), + variant='ghost', + ) + +""" +) + +button_group_sizes = ( +"""rx.chakra.button_group( + rx.chakra.button('Large Button', size='lg'), + rx.chakra.button('Medium Button', size='md'), + rx.chakra.button('Small Button', size='sm'), + ) + +""" +) + +button_group_disable = ( +"""rx.chakra.button_group( + rx.chakra.button('Option 1'), + rx.chakra.button('Option 2'), + rx.chakra.button('Option 3'), + is_disabled=True, + ) + +""" +) + +button_group_spacing = ( +"""rx.chakra.button_group( + rx.chakra.button('Option 1'), + rx.chakra.button('Option 2'), + rx.chakra.button('Option 3'), + spacing=8, + ) + +""" + +) +``` + +# Button Group + +The `rx.chakra.button_group` component allows you to create a group of buttons that are visually connected and styled together. +This is commonly used to group related actions or options in your application's user interface. + +## Basic Usage + +Here's an example of how to use the `rx.chakra.button_group` component to create a simple group of buttons: + +```python eval +docdemo(basic_button_group) +``` + +In this example, a button group is created with three buttons. The buttons are visually connected, and there +is a default spacing of `2` pixels between them. + +## Adjusting ButtonGroup Properties + +You can customize the appearance and behavior of the `rx.chakra.button_group` component by adjusting +its properties. For instance, you can set `is_attached` prop to `True` to make the buttons +appear flushed together: + +```python eval +docdemo(button_group_attached) +``` + +In this example, the `is_attached` property is set to `True`, resulting in the buttons having a seamless appearance. + +## ButtonGroup Variants + +Just like the `button` component, you can customize the visual style of your buttons using the `variant` prop. +This will apply to all buttons in the group. + +```python eval +docdemo(button_group_variant) +``` + +In this example, the `variant` prop is set to `ghost`, applying the variant style to all buttons in the group. + +## ButtonGroup Sizes + +Similarly, you can adjust the size of buttons within a button group using the `size` prop. +This prop allows you to choose from different size options, affecting all buttons within the group. + +```python eval +docdemo(button_group_sizes) +``` + +In this example, the `size` prop is used to set the size of all buttons within the group, with options such as `"lg"` (large), `"md"` (medium), and `"sm"` (small). + +## Disabling ButtonGroup + +You can also disable all the buttons within a button group by setting the `is_disabled` prop to `True`: + +```python eval +docdemo(button_group_disable) +``` + +In this case, all the buttons within the group will be disabled and unclickable. + +## Customizing Spacing + +The `spacing` prop allows you to control the gap between buttons within the group. Here's an example of setting a custom spacing of `8` pixels: + +```python eval +docdemo(button_group_spacing) +``` + +By setting `spacing` to `8`, the buttons will have a larger gap between them. + +```md alert info +- You can nest other components or elements within the button group to create more complex layouts. +- Button groups are a useful way to visually organize related actions or options in your application, providing a consistent and cohesive user interface. +- Experiment with different combinations of props to achieve the desired styling and behavior for your button groups in Reflex-based applications. +``` diff --git a/docs/library/chakra/forms/checkbox.md b/docs/library/chakra/forms/checkbox.md new file mode 100644 index 000000000..00ab81fb9 --- /dev/null +++ b/docs/library/chakra/forms/checkbox.md @@ -0,0 +1,69 @@ +--- +components: + - rx.chakra.Checkbox +--- + +# Checkbox + +A checkbox is a common way to toggle boolean value. +The checkbox component can be used on its own or in a group. + +```python exec +import reflex as rx +``` + +```python demo +rx.chakra.checkbox("Check Me!") +``` + +Checkboxes can range in size and styles. + +```python demo +rx.chakra.hstack( + rx.chakra.checkbox("Example", color_scheme="green", size="sm"), + rx.chakra.checkbox("Example", color_scheme="blue", size="sm"), + rx.chakra.checkbox("Example", color_scheme="yellow", size="md"), + rx.chakra.checkbox("Example", color_scheme="orange", size="md"), + rx.chakra.checkbox("Example", color_scheme="red", size="lg"), +) +``` + +Checkboxes can also have different visual states. + +```python demo +rx.chakra.hstack( + rx.chakra.checkbox( + "Example", color_scheme="green", size="lg", is_invalid=True + ), + rx.chakra.checkbox( + "Example", color_scheme="green", size="lg", is_disabled=True + ), +) +``` + +Checkboxes can be hooked up to a state using the `on_change` prop. + +```python demo exec +import reflex as rx + + +class CheckboxState(rx.State): + checked: bool = False + + def toggle(self): + self.checked = not self.checked + + +def checkbox_state_example(): + return rx.chakra.hstack( + rx.cond( + CheckboxState.checked, + rx.chakra.text("Checked", color="green"), + rx.chakra.text("Unchecked", color="red"), + ), + rx.chakra.checkbox( + "Example", + on_change=CheckboxState.set_checked, + ) + ) +``` diff --git a/docs/library/chakra/forms/editable.md b/docs/library/chakra/forms/editable.md new file mode 100644 index 000000000..91c115c1c --- /dev/null +++ b/docs/library/chakra/forms/editable.md @@ -0,0 +1,48 @@ +--- +components: + - rx.chakra.Editable + - rx.chakra.EditablePreview + - rx.chakra.EditableInput + - rx.chakra.EditableTextarea +--- + +```python exec +import reflex as rx +``` + +# Editable + +Editable is used for inline renaming of some text. +It appears as normal UI text but transforms into a text input field when the user clicks on or focuses it. + +```python demo exec +class EditableState(rx.State): + example_input: str + example_textarea: str + example_state: str + + def set_uppertext(self, example_state: str): + self.example_state = example_state.upper() + + +def editable_example(): + return rx.chakra.editable( + rx.chakra.editable_preview(), + rx.chakra.editable_input(), + placeholder="An input example...", + on_submit=EditableState.set_uppertext, + width="100%", + ) +``` + +Another variant of editable can be made with a textarea instead of an input. + +```python demo +rx.chakra.editable( + rx.chakra.editable_preview(), + rx.chakra.editable_textarea(), + placeholder="A textarea example...", + on_submit=EditableState.set_example_textarea, + width="100%", +) +``` diff --git a/docs/library/chakra/forms/form.md b/docs/library/chakra/forms/form.md new file mode 100644 index 000000000..68f156200 --- /dev/null +++ b/docs/library/chakra/forms/form.md @@ -0,0 +1,110 @@ +--- +components: + - rx.chakra.Form +--- + +```python exec +import reflex as rx +``` + +# Form + +Forms are used to collect user input. The `rx.chakra.form` component is used to group inputs and submit them together. + +The form component's children can be form controls such as `rx.chakra.input`, `rx.chakra.checkbox`, or `rx.chakra.switch`. The controls should have an `name` attribute that is used to identify the control in the form data. The `on_submit` event trigger submits the form data as a dictionary to the `handle_submit` event handler. + +The form is submitted when the user clicks the submit button or presses enter on the form controls. + +```python demo exec +class FormState(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_example(): + return rx.chakra.vstack( + rx.chakra.form( + rx.chakra.vstack( + rx.chakra.input(placeholder="First Name", name="first_name"), + rx.chakra.input(placeholder="Last Name", name="last_name"), + rx.chakra.hstack( + rx.chakra.checkbox("Checked", name="check"), + rx.chakra.switch("Switched", name="switch"), + ), + rx.chakra.button("Submit", type_="submit"), + ), + on_submit=FormState.handle_submit, + reset_on_submit=True, + ), + rx.chakra.divider(), + rx.chakra.heading("Results"), + rx.chakra.text(FormState.form_data.to_string()), + ) +``` + +```md alert warning +# When using the form you must include a button or input with `type_='submit'`. +``` + +## Dynamic Forms + +Forms can be dynamically created by iterating through state vars using `rx.foreach`. + +This example allows the user to add new fields to the form prior to submit, and all +fields will be included in the form data passed to the `handle_submit` function. + +```python demo exec +class DynamicFormState(rx.State): + form_data: dict = {} + form_fields: list[str] = ["first_name", "last_name", "email"] + + @rx.cached_var + def form_field_placeholders(self) -> list[str]: + return [ + " ".join(w.capitalize() for w in field.split("_")) + for field in self.form_fields + ] + + def add_field(self, form_data: dict): + new_field = form_data.get("new_field") + if not new_field: + return + field_name = new_field.strip().lower().replace(" ", "_") + self.form_fields.append(field_name) + + def handle_submit(self, form_data: dict): + self.form_data = form_data + + +def dynamic_form(): + return rx.chakra.vstack( + rx.chakra.form( + rx.chakra.vstack( + rx.foreach( + DynamicFormState.form_fields, + lambda field, idx: rx.chakra.input( + placeholder=DynamicFormState.form_field_placeholders[idx], + name=field, + ), + ), + rx.chakra.button("Submit", type_="submit"), + ), + on_submit=DynamicFormState.handle_submit, + reset_on_submit=True, + ), + rx.chakra.form( + rx.chakra.hstack( + rx.chakra.input(placeholder="New Field", name="new_field"), + rx.chakra.button("+", type_="submit"), + ), + on_submit=DynamicFormState.add_field, + reset_on_submit=True, + ), + rx.chakra.divider(), + rx.chakra.heading("Results"), + rx.chakra.text(DynamicFormState.form_data.to_string()), + ) +``` diff --git a/docs/library/chakra/forms/formcontrol.md b/docs/library/chakra/forms/formcontrol.md new file mode 100644 index 000000000..9f7aab129 --- /dev/null +++ b/docs/library/chakra/forms/formcontrol.md @@ -0,0 +1,51 @@ +--- +components: + - rx.chakra.FormControl + - rx.chakra.FormLabel + - rx.chakra.FormErrorMessage + - rx.chakra.FormHelperText +--- + +# Form Control + +Form control provides context such as filled/focused/error/required for form inputs. + +```python exec +import reflex as rx +``` + +```python demo +rx.chakra.form_control( + rx.chakra.form_label("First Name", html_for="email"), + rx.chakra.checkbox("Example"), + rx.chakra.form_helper_text("This is a help text"), + is_required=True, +) +``` + +The example below shows a form error when then name length is 3 or less. + +```python demo exec +import reflex as rx + +class FormErrorState(rx.State): + name: str + + @rx.var + def is_error(self) -> bool: + return len(self.name) <= 3 + +def form_state_example(): + return rx.chakra.vstack( + rx.chakra.form_control( + rx.chakra.input(placeholder="name", on_blur=FormErrorState.set_name), + rx.cond( + FormErrorState.is_error, + rx.chakra.form_error_message("Name should be more than four characters"), + rx.chakra.form_helper_text("Enter name"), + ), + is_invalid=FormErrorState.is_error, + is_required=True, + ) + ) +``` diff --git a/docs/library/chakra/forms/input.md b/docs/library/chakra/forms/input.md new file mode 100644 index 000000000..558905413 --- /dev/null +++ b/docs/library/chakra/forms/input.md @@ -0,0 +1,110 @@ +--- +components: + - rx.chakra.Input +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` + +# Input + +The input component is used to receive text input from the user. + +```python demo exec +class InputState(rx.State): + text: str = "Type something..." + +def basic_input_example(): + return rx.chakra.vstack( + rx.chakra.text(InputState.text, color_scheme="green"), + rx.chakra.input(value=InputState.text, on_change=InputState.set_text) + ) +``` + +"Behind the scene, the input component is implemented using debounced input to avoid sending individual state updates per character to the backend while the user is still typing. +This allows a state var to directly control the `value` prop from the backend without the user experiencing input lag. +For advanced use cases, you can tune the debounce delay by setting the `debounce_timeout` when creating the Input component. +You can find examples of how it is used in the [DebouncedInput]({library.forms.debounce.path}) component. + +```python demo exec +class ClearInputState(rx.State): + text: str + + def clear_text(self): + self.text = "" + + +def clear_input_example(): + return rx.chakra.vstack( + rx.chakra.text(ClearInputState.text), + rx.chakra.input( + on_change=ClearInputState.set_text, + value=ClearInputState.text, + ), + rx.chakra.button("Clear", on_click=ClearInputState.clear_text), + ) +``` + +The input component can also use the `on_blur` event handler to only change the state when the user clicks away from the input. +This is useful for performance reasons, as the state will only be updated when the user is done typing. + +```python demo exec +class InputBlurState(rx.State): + text: str = "Type something..." + + def set_text(self, text: str): + self.text = text.upper() + + +def input_blur_example(): + return rx.chakra.vstack( + rx.chakra.text(InputBlurState.text), + rx.chakra.input(placeholder="Type something...", on_blur=InputBlurState.set_text) + ) +``` + +You can change the type of input by using the `type_` prop. +For example you can create a password input or a date picker. + +```python demo +rx.chakra.vstack( + rx.chakra.input(type_="password"), + rx.chakra.input(type_="date"), +) +``` + +We also provide a `rx.chakra.password` component as a shorthand for the password input. + +```python demo +rx.chakra.password() +``` + +You can also use forms in combination with inputs. +This is useful for collecting multiple values with a single event handler and automatically supporting `Enter` key submit functionality that desktop users expect. + +```python demo exec +class InputFormState(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + +def input_form_example(): + return rx.chakra.vstack( + rx.chakra.form( + rx.chakra.vstack( + rx.chakra.input(placeholder="First Name", id="first_name"), + rx.chakra.input(placeholder="Last Name", id="last_name"), + rx.chakra.button("Submit", type_="submit"), + ), + on_submit=InputFormState.handle_submit, + ), + rx.chakra.divider(), + rx.chakra.heading("Results"), + rx.chakra.text(InputFormState.form_data.to_string()), + ) +``` + diff --git a/docs/library/chakra/forms/numberinput.md b/docs/library/chakra/forms/numberinput.md new file mode 100644 index 000000000..5d0d33a96 --- /dev/null +++ b/docs/library/chakra/forms/numberinput.md @@ -0,0 +1,28 @@ +--- +components: + - rx.chakra.NumberInput + - rx.chakra.NumberInputField + - rx.chakra.NumberInputStepper + - rx.chakra.NumberIncrementStepper + - rx.chakra.NumberDecrementStepper +--- + +```python exec +import reflex as rx +``` + +# NumberInput + +The NumberInput component is similar to the Input component, but it has controls for incrementing or decrementing numeric values. + +```python demo exec +class NumberInputState(rx.State): + number: int + + +def number_input_example(): + return rx.chakra.number_input( + value=NumberInputState.number, + on_change=NumberInputState.set_number, + ) +``` diff --git a/docs/library/chakra/forms/pininput.md b/docs/library/chakra/forms/pininput.md new file mode 100644 index 000000000..9aaeaddfa --- /dev/null +++ b/docs/library/chakra/forms/pininput.md @@ -0,0 +1,45 @@ +--- +components: + - rx.chakra.PinInput +--- + +```python exec +import reflex as rx +``` + +# PinInput + +The PinInput component is similar to the Input component, but it is optimized for entering sequences of digits + +```python demo exec +class PinInputState(rx.State): + pin: str + + +def basic_pininput_example(): + return rx.chakra.vstack( + rx.chakra.heading(PinInputState.pin), + rx.chakra.box( + rx.chakra.pin_input( + length=4, + on_change=PinInputState.set_pin, + mask=True, + ), + ), + ) +``` + +The PinInput component can also be customized as seen below. + +```python demo +rx.chakra.center( + rx.chakra.pin_input( + rx.chakra.pin_input_field(color="red"), + rx.chakra.pin_input_field(border_color="green"), + rx.chakra.pin_input_field(shadow="md"), + rx.chakra.pin_input_field(color="blue"), + rx.chakra.pin_input_field(border_radius="md"), + on_change=PinInputState.set_pin, + ) +) +``` diff --git a/docs/library/chakra/forms/radiogroup.md b/docs/library/chakra/forms/radiogroup.md new file mode 100644 index 000000000..431a1f266 --- /dev/null +++ b/docs/library/chakra/forms/radiogroup.md @@ -0,0 +1,74 @@ +--- +components: + - rx.chakra.RadioGroup + - rx.chakra.Radio +--- + +```python exec +import reflex as rx +``` + +# Radio + +Radios are used when only one choice may be selected in a series of options. + +```python demo exec +from typing import List +options: List[str] = ["Option 1", "Option 2", "Option 3"] + +class RadioState(rx.State): + text: str = "No Selection" + + +def basic_radio_example(): + return rx.chakra.vstack( + rx.chakra.badge(RadioState.text, color_scheme="green"), + rx.chakra.radio_group( + options, + on_change=RadioState.set_text, + ), + ) +``` + +The `default_value` and `default_checked` arguments can be used to set the default value of the radio group. + +```python demo +rx.chakra.vstack( + rx.chakra.radio_group( + options, + default_value="Option 2", + default_checked=True, + ), +) +``` + +A hstack with the `spacing` argument can be used to set the spacing between the radio buttons. + +```python demo +rx.chakra.radio_group( + rx.chakra.radio_group( + rx.chakra.hstack( + rx.foreach( + options, + lambda option: rx.chakra.radio(option), + ), + spacing="2em", + ), + ), +) +``` + +A vstack can be used to stack the radio buttons vertically. + +```python demo +rx.chakra.radio_group( + rx.chakra.radio_group( + rx.chakra.vstack( + rx.foreach( + options, + lambda option: rx.chakra.radio(option), + ), + ), + ), +) +``` diff --git a/docs/library/chakra/forms/rangeslider.md b/docs/library/chakra/forms/rangeslider.md new file mode 100644 index 000000000..7868a61f5 --- /dev/null +++ b/docs/library/chakra/forms/rangeslider.md @@ -0,0 +1,45 @@ +--- +components: + - rx.chakra.RangeSlider + - rx.chakra.RangeSliderTrack + - rx.chakra.RangeSliderFilledTrack + - rx.chakra.RangeSliderThumb +--- + +```python exec +import reflex as rx +``` + +# RangeSlider + +The range slider is used to allow users to make selections from a range of values. + +```python demo exec +from typing import List + +class RangeSliderState(rx.State): + value: List[int] = [0, 100] + + +def range_slider_example(): + return rx.chakra.vstack( + rx.chakra.heading(f"{RangeSliderState.value[0]} : {RangeSliderState.value[1]}"), + rx.chakra.range_slider( + on_change_end=RangeSliderState.set_value + ), + width="100%", + ) +``` + +If you want to trigger state change on every slider movement, you can use the `on_change` event handler. +This is not recommended for performance reasons and should only be used if you need to perform an event on every slider movement. + +```python demo +rx.chakra.vstack( + rx.chakra.heading(f"{RangeSliderState.value[0]} : {RangeSliderState.value[1]}"), + rx.chakra.range_slider( + on_change=RangeSliderState.set_value + ), + width="100%", +) +``` diff --git a/docs/library/chakra/forms/select.md b/docs/library/chakra/forms/select.md new file mode 100644 index 000000000..7795b52f3 --- /dev/null +++ b/docs/library/chakra/forms/select.md @@ -0,0 +1,86 @@ +--- +components: + - rx.chakra.Select +--- + +```python exec +import reflex as rx +``` + +# Select + +The Select component is used to create a list of options, which allows a user to select one or more options from it. + +```python demo exec +from typing import List +options: List[str] = ["Option 1", "Option 2", "Option 3"] + +class SelectState(rx.State): + option: str = "No selection yet." + + +def basic_select_example(): + return rx.chakra.vstack( + rx.chakra.heading(SelectState.option), + rx.chakra.select( + options, + placeholder="Select an example.", + on_change=SelectState.set_option, + color_schemes="twitter", + ), + ) +``` + +Select can also have multiple options selected at once. + +```python demo exec +from typing import List +options: List[str] = ["Option 1", "Option 2", "Option 3"] + +class MultiSelectState(rx.State): + option: List[str] = [] + + +def multiselect_example(): + return rx.chakra.vstack( + rx.chakra.heading(MultiSelectState.option), + rx.chakra.select( + options, + placeholder="Select examples", + is_multi=True, + on_change=MultiSelectState.set_option, + close_menu_on_select=False, + color_schemes="twitter", + ), + ) +``` + +The component can also be customized and styled as seen in the next examples. + +```python demo +rx.chakra.vstack( + rx.chakra.select(options, placeholder="Select an example.", size="xs"), + rx.chakra.select(options, placeholder="Select an example.", size="sm"), + rx.chakra.select(options, placeholder="Select an example.", size="md"), + rx.chakra.select(options, placeholder="Select an example.", size="lg"), +) +``` + +```python demo +rx.chakra.vstack( + rx.chakra.select(options, placeholder="Select an example.", variant="outline"), + rx.chakra.select(options, placeholder="Select an example.", variant="filled"), + rx.chakra.select(options, placeholder="Select an example.", variant="flushed"), + rx.chakra.select(options, placeholder="Select an example.", variant="unstyled"), +) +``` + +```python demo +rx.chakra.select( + options, + placeholder="Select an example.", + color="white", + bg="#68D391", + border_color="#38A169", +) +``` diff --git a/docs/library/chakra/forms/slider.md b/docs/library/chakra/forms/slider.md new file mode 100644 index 000000000..df7a9d90c --- /dev/null +++ b/docs/library/chakra/forms/slider.md @@ -0,0 +1,100 @@ +--- +components: + - rx.chakra.Slider + - rx.chakra.SliderTrack + - rx.chakra.SliderFilledTrack + - rx.chakra.SliderThumb + - rx.chakra.SliderMark +--- + +```python exec +import reflex as rx +``` + +# Slider + +The Slider is used to allow users to make selections from a range of values. + +```python demo exec +class SliderState(rx.State): + value: int = 50 + + +def slider_example(): + return rx.chakra.vstack( + rx.chakra.heading(SliderState.value), + rx.chakra.slider( + on_change=SliderState.set_value + ), + width="100%", + ) +``` + +You can also combine all three event handlers: `on_change`, `on_change_start`, and `on_change_end`. + +```python demo exec +class SliderCombo(rx.State): + value: int = 50 + color: str = "black" + + def set_start(self, value): + self.color = "#68D391" + + def set_end(self, value): + self.color = "#F56565" + + +def slider_combo_example(): + return rx.chakra.vstack( + rx.chakra.heading(SliderCombo.value, color=SliderCombo.color), + rx.chakra.slider( + on_change_start=SliderCombo.set_start, + on_change=SliderCombo.set_value, + on_change_end=SliderCombo.set_end, + ), + width="100%", + ) +``` + +You can also customize the appearance of the slider by passing in custom components for the track and thumb. + +```python demo exec +class SliderManual(rx.State): + value: int = 50 + + def set_end(self, value: int): + self.value = value + + +def slider_manual_example(): + return rx.chakra.vstack( + rx.chakra.heading(f"Weather is {SliderManual.value} degrees"), + rx.chakra.slider( + rx.chakra.slider_track( + rx.chakra.slider_filled_track(bg="tomato"), + bg='red.100' + ), + rx.chakra.slider_thumb( + rx.chakra.icon(tag="sun", color="white"), + box_size="1.5em", + bg="tomato", + ), + on_change_end=SliderManual.set_end, + ), + width="100%", + ) +``` + +If you want to trigger state change on every slider movement, you can use the `on_change` event handler. + +For performance reasons, you may want to trigger state change only when the user releases the slider by using the `on_change_end` event handler, but if you need perform an event on every slider movement, you can use the `on_change` event handler. + +```python demo +rx.chakra.vstack( + rx.chakra.heading(SliderState.value), + rx.chakra.slider( + on_change=SliderState.set_value + ), + width="100%", +) +``` diff --git a/docs/library/chakra/forms/switch.md b/docs/library/chakra/forms/switch.md new file mode 100644 index 000000000..441a3f12d --- /dev/null +++ b/docs/library/chakra/forms/switch.md @@ -0,0 +1,48 @@ +--- +components: + - rx.chakra.Switch +--- + +```python exec +import reflex as rx +``` + +# Switch + +The Switch component is used as an alternative for the Checkbox component. +You can switch between enabled or disabled states. + +```python demo exec +class SwitchState1(rx.State): + checked: bool = False + is_checked: bool = "Switch off!" + + def change_check(self, checked: bool): + self.checked = checked + if self.checked: + self.is_checked = "Switch on!" + else: + self.is_checked = "Switch off!" + + +def switch_example(): + return rx.chakra.vstack( + rx.chakra.heading(SwitchState1.is_checked), + rx.chakra.switch( + is_checked=SwitchState1.checked, on_change=SwitchState1.change_check + ), + ) +``` + +You can also change the color scheme of the Switch component by passing the `color_scheme` argument. +The default color scheme is blue. + +```python demo +rx.chakra.hstack( + rx.chakra.switch(color_scheme="red"), + rx.chakra.switch(color_scheme="green"), + rx.chakra.switch(color_scheme="yellow"), + rx.chakra.switch(color_scheme="blue"), + rx.chakra.switch(color_scheme="purple"), +) +``` diff --git a/docs/library/chakra/forms/textarea.md b/docs/library/chakra/forms/textarea.md new file mode 100644 index 000000000..2fbc8cd47 --- /dev/null +++ b/docs/library/chakra/forms/textarea.md @@ -0,0 +1,30 @@ +--- +components: + - rx.chakra.TextArea +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` + +# Textarea + +The TextArea component allows you to easily create multi-line text inputs. + +```python demo exec +class TextareaState(rx.State): + text: str = "Hello World!" + +def textarea_example(): + return rx.chakra.vstack( + rx.chakra.heading(TextareaState.text), + rx.chakra.text_area(value=TextareaState.text, on_change=TextareaState.set_text) + ) +``` + +Alternatively, you can use the `on_blur` event handler to only update the state when the user clicks away. + +Similar to the Input component, the TextArea is also implemented using debounced input when it is fully controlled. +You can tune the debounce delay by setting the `debounce_timeout` prop. +You can find examples of how it is used in the [DebouncedInput]({library.forms.debounce.path}) component. diff --git a/docs/library/chakra/layout/aspectratio.md b/docs/library/chakra/layout/aspectratio.md new file mode 100644 index 000000000..5eb2c7d52 --- /dev/null +++ b/docs/library/chakra/layout/aspectratio.md @@ -0,0 +1,27 @@ +--- +components: + - rx.chakra.AspectRatio +--- + +# Aspect Ratio + +```python exec +import reflex as rx +``` + +Preserve the ratio of the components contained within a region. + +```python demo +rx.chakra.box(element="iframe", src="https://bit.ly/naruto-sage", border_color="red") +``` + +```python demo +rx.chakra.aspect_ratio( + rx.chakra.box( + element="iframe", + src="https://bit.ly/naruto-sage", + border_color="red" + ), + ratio=4/3 +) +``` diff --git a/docs/library/chakra/layout/box.md b/docs/library/chakra/layout/box.md new file mode 100644 index 000000000..14d52aac1 --- /dev/null +++ b/docs/library/chakra/layout/box.md @@ -0,0 +1,46 @@ +--- +components: + - rx.chakra.Box +--- + +# Box + +```python exec +import reflex as rx +``` + +Box is a generic container component that can be used to group other components. + +```python demo +rx.chakra.vstack( + rx.chakra.box("Example", bg="yellow", border_radius="sm", width="20%"), + rx.chakra.box("Example", bg="orange", border_radius="md", width="40%"), + rx.chakra.box("Example", bg="red", border_radius="md", width="60%"), + rx.chakra.box("Example", bg="lightblue", border_radius="lg", width="80%"), + rx.chakra.box("Example", bg="lightgreen", border_radius="xl", width="100%"), + width="100%", +) +``` + +Below is an example of how a box component can contain other components. + +```python demo +rx.chakra.box( + rx.chakra.button("Click Me"), + bg="lightgreen", + border_radius="15px", + border_color="green", + border_width="thick", + padding=5, +) +``` + +Box can also compose videos and iframe elements. + +```python demo +rx.chakra.box( + element= "iframe", + src="https://www.youtube.com/embed/9bZkp7q19f0", + width = "100%", +) +``` diff --git a/docs/library/chakra/layout/card.md b/docs/library/chakra/layout/card.md new file mode 100644 index 000000000..8fe1e5f0e --- /dev/null +++ b/docs/library/chakra/layout/card.md @@ -0,0 +1,25 @@ +--- +components: + - rx.chakra.Card + - rx.chakra.CardHeader + - rx.chakra.CardBody + - rx.chakra.CardFooter +--- + +```python exec +import reflex as rx +``` + +# Card + +Card is a flexible component used to group and display content in a clear and concise format. + +```python demo +rx.chakra.card( + rx.chakra.text("Body of the Card Component"), + header=rx.chakra.heading("Header", size="lg"), + footer=rx.chakra.heading("Footer",size="sm"), +) +``` + +You can pass a header with `header=` and/or a footer with `footer=`. diff --git a/docs/library/chakra/layout/center.md b/docs/library/chakra/layout/center.md new file mode 100644 index 000000000..aa315a16f --- /dev/null +++ b/docs/library/chakra/layout/center.md @@ -0,0 +1,43 @@ +--- +components: + - rx.chakra.Center + - rx.chakra.Circle + - rx.chakra.Square +--- + +# Center + +```python exec +import reflex as rx +``` + +Center, Square, and Circle are components that center its children within itself. + +```python demo +rx.chakra.center( + rx.chakra.text("Hello World!"), + border_radius="15px", + border_width="thick", + width="50%", +) +``` + +Below are examples of circle and square. + +```python demo +rx.chakra.hstack( + rx.chakra.square( + rx.chakra.vstack(rx.chakra.text("Square")), + border_width="thick", + border_color="purple", + padding="1em", + ), + rx.chakra.circle( + rx.chakra.vstack(rx.chakra.text("Circle")), + border_width="thick", + border_color="orange", + padding="1em", + ), + spacing="2em", +) +``` diff --git a/docs/library/chakra/layout/container.md b/docs/library/chakra/layout/container.md new file mode 100644 index 000000000..ad8dcaabe --- /dev/null +++ b/docs/library/chakra/layout/container.md @@ -0,0 +1,20 @@ +--- +components: + - rx.chakra.Container +--- + +# Container + +```python exec +import reflex as rx +``` + +Containers are used to constrain a content's width to the current breakpoint, while keeping it fluid. + +```python demo +rx.chakra.container( + rx.chakra.box("Example", bg="blue", color="white", width="50%"), + center_content=True, + bg="lightblue", +) +``` diff --git a/docs/library/chakra/layout/flex.md b/docs/library/chakra/layout/flex.md new file mode 100644 index 000000000..79c1ac020 --- /dev/null +++ b/docs/library/chakra/layout/flex.md @@ -0,0 +1,33 @@ +--- +components: + - rx.chakra.Flex +--- + +# Flex + +```python exec +import reflex as rx +``` + +Flexbox is a layout model that allows elements to align and distribute space within a container. Using flexible widths and heights, elements can be aligned to fill a space or distribute space between elements, which makes it a great tool to use for responsive design systems. + +```python demo +rx.chakra.flex( + rx.chakra.center("Center", bg="lightblue"), + rx.chakra.square("Square", bg="lightgreen", padding=10), + rx.chakra.box("Box", bg="salmon", width="150px"), +) +``` + +Combining flex with spacer allows for stackable and responsive components. + +```python demo +rx.chakra.flex( + rx.chakra.center("Center", bg="lightblue"), + rx.chakra.spacer(), + rx.chakra.square("Square", bg="lightgreen", padding=10), + rx.chakra.spacer(), + rx.chakra.box("Box", bg="salmon", width="150px"), + width = "100%", +) +``` diff --git a/docs/library/chakra/layout/grid.md b/docs/library/chakra/layout/grid.md new file mode 100644 index 000000000..634a57b15 --- /dev/null +++ b/docs/library/chakra/layout/grid.md @@ -0,0 +1,43 @@ +--- +components: + - rx.chakra.Grid + - rx.chakra.GridItem +--- + +# Grid + +```python exec +import reflex as rx +``` + +A primitive useful for grid layouts. Grid is Box with display, grid and it comes with helpful style shorthand. It renders a div element. + +```python demo +rx.chakra.grid( + rx.chakra.grid_item(row_span=1, col_span=1, bg="lightgreen"), + rx.chakra.grid_item(row_span=1, col_span=1, bg="lightblue"), + rx.chakra.grid_item(row_span=1, col_span=1, bg="purple"), + rx.chakra.grid_item(row_span=1, col_span=1, bg="orange"), + rx.chakra.grid_item(row_span=1, col_span=1, bg="yellow"), + template_columns="repeat(5, 1fr)", + h="10em", + width="100%", + gap=4, +) +``` + +In some layouts, you may need certain grid items to span specific amount of columns or rows instead of an even distribution. To achieve this, you need to pass the col_span prop to the GridItem component to span across columns and also pass the row_span component to span across rows. You also need to specify the template_columns and template_rows. + +```python demo +rx.chakra.grid( + rx.chakra.grid_item(row_span=2, col_span=1, bg="lightblue"), + rx.chakra.grid_item(col_span=2, bg="lightgreen"), + rx.chakra.grid_item(col_span=2, bg="yellow"), + rx.chakra.grid_item(col_span=4, bg="orange"), + template_rows="repeat(2, 1fr)", + template_columns="repeat(5, 1fr)", + h="200px", + width="100%", + gap=4, +) +``` diff --git a/docs/library/chakra/layout/responsivegrid.md b/docs/library/chakra/layout/responsivegrid.md new file mode 100644 index 000000000..006eb53d3 --- /dev/null +++ b/docs/library/chakra/layout/responsivegrid.md @@ -0,0 +1,39 @@ +--- +components: + - rx.chakra.ResponsiveGrid +--- + +# Responsive Grid + +```python exec +import reflex as rx +``` + +ResponsiveGrid provides a friendly interface to create responsive grid layouts with ease. SimpleGrid composes Box so you can pass all the Box props and css grid props with addition to the ones below. + +Specify a fixed number of columns for the grid layout. + +```python demo +rx.chakra.responsive_grid( + rx.chakra.box(height="5em", width="5em", bg="lightgreen"), + rx.chakra.box(height="5em", width="5em", bg="lightblue"), + rx.chakra.box(height="5em", width="5em", bg="purple"), + rx.chakra.box(height="5em", width="5em", bg="tomato"), + rx.chakra.box(height="5em", width="5em", bg="orange"), + rx.chakra.box(height="5em", width="5em", bg="yellow"), + columns=[3], + spacing="4", +) +``` + +```python demo +rx.chakra.responsive_grid( + rx.chakra.box(height="5em", width="5em", bg="lightgreen"), + rx.chakra.box(height="5em", width="5em", bg="lightblue"), + rx.chakra.box(height="5em", width="5em", bg="purple"), + rx.chakra.box(height="5em", width="5em", bg="tomato"), + rx.chakra.box(height="5em", width="5em", bg="orange"), + rx.chakra.box(height="5em", width="5em", bg="yellow"), + columns=[1, 2, 3, 4, 5, 6], +) +``` diff --git a/docs/library/chakra/layout/spacer.md b/docs/library/chakra/layout/spacer.md new file mode 100644 index 000000000..b95792fbe --- /dev/null +++ b/docs/library/chakra/layout/spacer.md @@ -0,0 +1,23 @@ +--- +components: + - rx.chakra.Spacer +--- + +# Spacer + +```python exec +import reflex as rx +``` + +Creates an adjustable, empty space that can be used to tune the spacing between child elements within Flex. + +```python demo +rx.chakra.flex( + rx.chakra.center(rx.chakra.text("Example"), bg="lightblue"), + rx.chakra.spacer(), + rx.chakra.center(rx.chakra.text("Example"), bg="lightgreen"), + rx.chakra.spacer(), + rx.chakra.center(rx.chakra.text("Example"), bg="salmon"), + width="100%", +) +``` diff --git a/docs/library/chakra/layout/stack.md b/docs/library/chakra/layout/stack.md new file mode 100644 index 000000000..e78a9f0d2 --- /dev/null +++ b/docs/library/chakra/layout/stack.md @@ -0,0 +1,36 @@ +--- +components: + - rx.chakra.Stack + - rx.chakra.Hstack + - rx.chakra.Vstack +--- + +# Stack + +```python exec +import reflex as rx +``` + +Below are two examples the different types of stack components vstack and hstack for ordering items on a page. + +```python demo +rx.chakra.hstack( + rx.chakra.box("Example", bg="red", border_radius="md", width="10%"), + rx.chakra.box("Example", bg="orange", border_radius="md", width="10%"), + rx.chakra.box("Example", bg="yellow", border_radius="md", width="10%"), + rx.chakra.box("Example", bg="lightblue", border_radius="md", width="10%"), + rx.chakra.box("Example", bg="lightgreen", border_radius="md", width="60%"), + width="100%", +) +``` + +```python demo +rx.chakra.vstack( + rx.chakra.box("Example", bg="red", border_radius="md", width="20%"), + rx.chakra.box("Example", bg="orange", border_radius="md", width="40%"), + rx.chakra.box("Example", bg="yellow", border_radius="md", width="60%"), + rx.chakra.box("Example", bg="lightblue", border_radius="md", width="80%"), + rx.chakra.box("Example", bg="lightgreen", border_radius="md", width="100%"), + width="100%", +) +``` diff --git a/docs/library/chakra/layout/wrap.md b/docs/library/chakra/layout/wrap.md new file mode 100644 index 000000000..87e5d2150 --- /dev/null +++ b/docs/library/chakra/layout/wrap.md @@ -0,0 +1,27 @@ +--- +components: + - rx.chakra.Wrap + - rx.chakra.WrapItem +--- + +# Wrap + +```python exec +import reflex as rx +``` + +Wrap is a layout component that adds a defined space between its children. + +It wraps its children automatically if there isn't enough space to fit any more in the same row. Think of it as a smarter flex-wrap with spacing support. + +```python demo +rx.chakra.wrap( + rx.chakra.wrap_item(rx.chakra.box("Example", bg="lightgreen", w="100px", h="80px")), + rx.chakra.wrap_item(rx.chakra.box("Example", bg="lightblue", w="200px", h="80px")), + rx.chakra.wrap_item(rx.chakra.box("Example", bg="red", w="300px", h="80px")), + rx.chakra.wrap_item(rx.chakra.box("Example", bg="orange", w="400px", h="80px")), + width="100%", + spacing="2em", + align="center", +) +``` diff --git a/docs/library/chakra/media/avatar.md b/docs/library/chakra/media/avatar.md new file mode 100644 index 000000000..cd2e3e052 --- /dev/null +++ b/docs/library/chakra/media/avatar.md @@ -0,0 +1,62 @@ +--- +components: + - rx.chakra.Avatar + - rx.chakra.AvatarBadge + - rx.chakra.AvatarGroup +--- + +```python exec +import reflex as rx +``` + +# Avatar + +The Avatar component is used to represent a user, and displays the profile picture, initials or fallback icon. + +```python demo +rx.chakra.hstack( + rx.chakra.avatar(size="sm"), + rx.chakra.avatar(name="Barack Obama", size="md"), + rx.chakra.avatar(name="Donald Trump", size="lg"), + rx.chakra.avatar(name="Joe Biden", size="xl"), +) +``` + +Avatar components can be grouped into avatar groups for easier display. + +```python demo +rx.chakra.avatar_group( + rx.chakra.avatar(name="Barack Obama"), + rx.chakra.avatar(name="Donald Trump"), + rx.chakra.avatar(name="Joe Biden"), +) +``` + +Badges can also be applied to show elements about the avatar such as activity. + +```python demo +rx.chakra.avatar_group( + rx.chakra.avatar( + rx.chakra.avatar_badge( + box_size="1.25em", bg="green.500", border_color="green.500" + ), + name="Barack Obama", + ), + rx.chakra.avatar( + rx.chakra.avatar_badge( + box_size="1.25em", bg="yellow.500", border_color="red.500" + ), + name="Donald Trump", + ), +) +``` + +If there are too many avatar to display a limit can be set using the `max_` prop. + +```python demo +rx.chakra.avatar_group( + *([rx.chakra.avatar(name="Barack Obama")] * 5), + size="md", + max_=3, +) +``` diff --git a/docs/library/chakra/media/icon.md b/docs/library/chakra/media/icon.md new file mode 100644 index 000000000..2f5893464 --- /dev/null +++ b/docs/library/chakra/media/icon.md @@ -0,0 +1,44 @@ +--- +components: + - rx.chakra.Icon +--- + +```python exec +import reflex as rx +from reflex.components.media.icon import ICON_LIST +``` + +# Icon + +The Icon component is used to display an icon from a library of icons. + +```python demo +rx.chakra.icon(tag="calendar") +``` + +Use the tag prop to specify the icon to display. + +```md alert success +Below is a list of all available icons. +``` + +```python eval +rx.chakra.box( + rx.chakra.divider(), + rx.chakra.responsive_grid( + *[ + rx.chakra.vstack( + rx.chakra.icon(tag=icon), + rx.chakra.text(icon), + bg="white", + border="1px solid #EAEAEA", + border_radius="0.5em", + padding=".75em", + ) + for icon in ICON_LIST + ], + columns=[2, 2, 3, 3, 4], + spacing="1em", + ) +) +``` diff --git a/docs/library/chakra/media/image.md b/docs/library/chakra/media/image.md new file mode 100644 index 000000000..0210cc0bb --- /dev/null +++ b/docs/library/chakra/media/image.md @@ -0,0 +1,54 @@ +--- +components: + - rx.chakra.Image +--- + +```python exec +import reflex as rx +``` + +# Image + +The Image component can display an image given a `src` path as an argument. +This could either be a local path from the assets folder or an external link. + +```python demo +rx.chakra.image(src="/reflex_banner.png", width="100px", height="auto") +``` + +Image composes a box and can be styled simlarly. + +```python demo +rx.chakra.image( + src="/reflex_banner.png", + width="100px", + height="auto", + border_radius="15px 50px", + border="5px solid #555", + box_shadow="lg", +) +``` + +You can also pass a `PIL` image object as the `src`. + +```python demo box +rx.chakra.vstack( + rx.chakra.image(src="https://picsum.photos/id/1/200/300", alt="An Unsplash Image") +) +``` + +```python +from PIL import Image +import requests + + +class ImageState(rx.State): + url = f"https://picsum.photos/id/1/200/300" + image = Image.open(requests.get(url, stream=True).raw) + + +def image_pil_example(): + return rx.chakra.vstack( + rx.chakra.image(src=ImageState.image) + ) +``` diff --git a/docs/library/chakra/navigation/breadcrumb.md b/docs/library/chakra/navigation/breadcrumb.md new file mode 100644 index 000000000..e60bb739f --- /dev/null +++ b/docs/library/chakra/navigation/breadcrumb.md @@ -0,0 +1,36 @@ +--- +components: + - rx.chakra.Breadcrumb + - rx.chakra.BreadcrumbItem + - rx.chakra.BreadcrumbLink +--- + +```python exec +import reflex as rx +``` + +# Breadcrumb + +Breadcrumbs, or a breadcrumb navigation, can help enhance how users navigate to previous page levels of a website. + +This is userful for websites with a lot of pages. + +```python demo +rx.chakra.breadcrumb( + rx.chakra.breadcrumb_item(rx.chakra.breadcrumb_link("Home", href="#")), + rx.chakra.breadcrumb_item(rx.chakra.breadcrumb_link("Docs", href="#")), + rx.chakra.breadcrumb_item(rx.chakra.breadcrumb_link("Breadcrumb", href="#")), +) +``` + +The separator prop can be used to change the default separator. + +```python demo +rx.chakra.breadcrumb( + rx.chakra.breadcrumb_item(rx.chakra.breadcrumb_link("Home", href="#")), + rx.chakra.breadcrumb_item(rx.chakra.breadcrumb_link("Docs", href="#")), + rx.chakra.breadcrumb_item(rx.chakra.breadcrumb_link("Breadcrumb", href="#")), + separator=">", + color="rgb(107,99,246)" +) +``` diff --git a/docs/library/chakra/navigation/link.md b/docs/library/chakra/navigation/link.md new file mode 100644 index 000000000..67321ca0c --- /dev/null +++ b/docs/library/chakra/navigation/link.md @@ -0,0 +1,41 @@ +--- +components: + - rx.chakra.Link +--- + +```python exec +import reflex as rx +``` + +# Link + +Links are accessible elements used primarily for navigation. + +```python demo +rx.chakra.link("Example", href="https://reflex.dev", color="rgb(107,99,246)") +``` + +You can also provide local links to other pages in your project without writing the full url. + +```python demo +rx.chakra.link("Example", href="/docs/library", color="rgb(107,99,246)") +``` + +The link component can be used to wrap other components to make them link to other pages. + +```python demo +rx.chakra.link(rx.chakra.button("Example"), href="https://reflex.dev", color="rgb(107,99,246)", button=True) +``` + +You can also create anchors to link to specific parts of a page using the id prop. + +```python demo +rx.chakra.box("Example", id="example") +``` + +To reference an anchor, you can use the href prop of the link component. +The `href` should be in the format of the page you want to link to followed by a `#` and the `id` of the anchor. + +```python demo +rx.chakra.link("Example", href="/docs/library/navigation/link#example", color="rgb(107,99,246)") +``` diff --git a/docs/library/chakra/navigation/linkoverlay.md b/docs/library/chakra/navigation/linkoverlay.md new file mode 100644 index 000000000..35a5c56f8 --- /dev/null +++ b/docs/library/chakra/navigation/linkoverlay.md @@ -0,0 +1,18 @@ +--- +components: + - rx.chakra.LinkOverlay +--- + +```python exec +import reflex as rx +``` + +# LinkOverlay + +Link overlay provides a semantic way to wrap elements (cards, blog post, articles, etc.) in a link. + +```python +rx.chakra.link_overlay( + rx.chakra.box("Example", bg="black", color="white", font_size=30), +) +``` diff --git a/docs/library/chakra/overlay/alertdialog.md b/docs/library/chakra/overlay/alertdialog.md new file mode 100644 index 000000000..d21a57da1 --- /dev/null +++ b/docs/library/chakra/overlay/alertdialog.md @@ -0,0 +1,45 @@ +--- +components: + - rx.chakra.AlertDialog + - rx.chakra.AlertDialogOverlay + - rx.chakra.AlertDialogContent + - rx.chakra.AlertDialogHeader + - rx.chakra.AlertDialogBody + - rx.chakra.AlertDialogFooter +--- + +```python exec +import reflex as rx +``` + +# AlertDialog + +AlertDialog component is used to interrupt the user with a mandatory confirmation or event. +The component will appear in front of the page prompting the user to conplete an event. + +```python demo exec +class AlertDialogState(rx.State): + show: bool = False + + def change(self): + self.show = not (self.show) + + +def alertdialog_example(): + return rx.chakra.vstack( + rx.chakra.button("Show Alert Dialog", on_click=AlertDialogState.change), + rx.chakra.alert_dialog( + rx.chakra.alert_dialog_overlay( + rx.chakra.alert_dialog_content( + rx.chakra.alert_dialog_header("Confirm"), + rx.chakra.alert_dialog_body("Do you want to confirm example?"), + rx.chakra.alert_dialog_footer( + rx.chakra.button("Close", on_click=AlertDialogState.change) + ), + ) + ), + is_open=AlertDialogState.show, + ), + width="100%", + ) +``` diff --git a/docs/library/chakra/overlay/drawer.md b/docs/library/chakra/overlay/drawer.md new file mode 100644 index 000000000..b77ef4d71 --- /dev/null +++ b/docs/library/chakra/overlay/drawer.md @@ -0,0 +1,70 @@ +--- +components: + - rx.chakra.Drawer + - rx.chakra.DrawerOverlay + - rx.chakra.DrawerContent + - rx.chakra.DrawerHeader + - rx.chakra.DrawerBody + - rx.chakra.DrawerFooter +--- + +```python exec +import reflex as rx +``` + +# Drawer + +The Drawer component is a panel that slides out from the edge of the screen. +It can be useful when you need users to complete a task or view some details without leaving the current page. + +```python demo exec +class DrawerState(rx.State): + show_right: bool = False + show_top: bool = False + + def top(self): + self.show_top = not (self.show_top) + + def right(self): + self.show_right = not (self.show_right) + +def drawer_example(): + return rx.chakra.vstack( + rx.chakra.button("Show Right Drawer", on_click=DrawerState.right), + rx.chakra.drawer( + rx.chakra.drawer_overlay( + rx.chakra.drawer_content( + rx.chakra.drawer_header("Confirm"), + rx.chakra.drawer_body("Do you want to confirm example?"), + rx.chakra.drawer_footer( + rx.chakra.button("Close", on_click=DrawerState.right) + ), + bg = "rgba(0, 0, 0, 0.3)" + ) + ), + is_open=DrawerState.show_right, + ) + ) +``` + +Drawer can display from the top, bottom, left, or right. +By defualt it displays to the right as seen above + +```python demo +rx.chakra.vstack( + rx.chakra.button("Show Top Drawer", on_click=DrawerState.top), + rx.chakra.drawer( + rx.chakra.drawer_overlay( + rx.chakra.drawer_content( + rx.chakra.drawer_header("Confirm"), + rx.chakra.drawer_body("Do you want to confirm example?"), + rx.chakra.drawer_footer( + rx.chakra.button("Close", on_click=DrawerState.top) + ), + ) + ), + placement="top", + is_open=DrawerState.show_top, + ) +) +``` diff --git a/docs/library/chakra/overlay/menu.md b/docs/library/chakra/overlay/menu.md new file mode 100644 index 000000000..94c2e7032 --- /dev/null +++ b/docs/library/chakra/overlay/menu.md @@ -0,0 +1,31 @@ +--- +components: + - rx.chakra.Menu + - rx.chakra.MenuButton + - rx.chakra.MenuList + - rx.chakra.MenuItem + - rx.chakra.MenuDivider + - rx.chakra.MenuGroup + - rx.chakra.MenuOptionGroup + - rx.chakra.MenuItemOption +--- + +```python exec +import reflex as rx +``` + +# Menu + +An accessible dropdown menu for the common dropdown menu button design pattern. + +```python demo +rx.chakra.menu( + rx.chakra.menu_button("Menu"), + rx.chakra.menu_list( + rx.chakra.menu_item("Example"), + rx.chakra.menu_divider(), + rx.chakra.menu_item("Example"), + rx.chakra.menu_item("Example"), + ), +) +``` diff --git a/docs/library/chakra/overlay/modal.md b/docs/library/chakra/overlay/modal.md new file mode 100644 index 000000000..065b36854 --- /dev/null +++ b/docs/library/chakra/overlay/modal.md @@ -0,0 +1,42 @@ +--- +components: + - rx.chakra.Modal + - rx.chakra.ModalOverlay + - rx.chakra.ModalContent + - rx.chakra.ModalHeader + - rx.chakra.ModalBody + - rx.chakra.ModalFooter +--- + +```python exec +import reflex as rx +``` + +# Modal + +A modal dialog is a window overlaid on either the primary window or another dialog window. +Content behind a modal dialog is inert, meaning that users cannot interact with it. + +```python demo exec +class ModalState(rx.State): + show: bool = False + + def change(self): + self.show = not (self.show) + + +def modal_example(): + return rx.chakra.vstack( + rx.chakra.button("Confirm", on_click=ModalState.change), + rx.chakra.modal( + rx.chakra.modal_overlay( + rx.chakra.modal_content( + rx.chakra.modal_header("Confirm"), + rx.chakra.modal_body("Do you want to confirm example?"), + rx.chakra.modal_footer(rx.chakra.button("Close", on_click=ModalState.change)), + ) + ), + is_open=ModalState.show, + ), +) +``` diff --git a/docs/library/chakra/overlay/popover.md b/docs/library/chakra/overlay/popover.md new file mode 100644 index 000000000..95a30da4e --- /dev/null +++ b/docs/library/chakra/overlay/popover.md @@ -0,0 +1,32 @@ +--- +components: + - rx.chakra.Popover + - rx.chakra.PopoverTrigger + - rx.chakra.PopoverContent + - rx.chakra.PopoverHeader + - rx.chakra.PopoverBody + - rx.chakra.PopoverFooter + - rx.chakra.PopoverArrow + - rx.chakra.PopoverAnchor +--- + +```python exec +import reflex as rx +``` + +# Popover + +Popover is a non-modal dialog that floats around a trigger. +It is used to display contextual information to the user, and should be paired with a clickable trigger element. + +```python demo +rx.chakra.popover( + rx.chakra.popover_trigger(rx.chakra.button("Popover Example")), + rx.chakra.popover_content( + rx.chakra.popover_header("Confirm"), + rx.chakra.popover_body("Do you want to confirm example?"), + rx.chakra.popover_footer(rx.chakra.text("Footer text.")), + rx.chakra.popover_close_button(), + ), +) +``` diff --git a/docs/library/chakra/overlay/tooltip.md b/docs/library/chakra/overlay/tooltip.md new file mode 100644 index 000000000..49db81068 --- /dev/null +++ b/docs/library/chakra/overlay/tooltip.md @@ -0,0 +1,20 @@ +--- +components: + - rx.chakra.Tooltip +--- + +```python exec +import reflex as rx +``` + +# Tooltip + +A tooltip is a brief, informative message that appears when a user interacts with an element. +Tooltips are usually initiated in one of two ways: through a mouse-hover gesture or through a keyboard-hover gesture. + +```python demo +rx.chakra.tooltip( + rx.chakra.text("Example", font_size=30), + label="Tooltip helper.", +) +``` diff --git a/docs/library/chakra/typography/heading.md b/docs/library/chakra/typography/heading.md new file mode 100644 index 000000000..4c5fb8663 --- /dev/null +++ b/docs/library/chakra/typography/heading.md @@ -0,0 +1,36 @@ +--- +components: + - rx.chakra.Heading +--- + +```python exec +import reflex as rx +``` + +# Heading + +The Heading component takes in a string and displays it as a heading. + +```python demo +rx.chakra.heading("Hello World!") +``` + +The size can be changed using the `size` prop. + +```python demo +rx.chakra.vstack( + rx.chakra.heading("Hello World!", size= "sm", color="red"), + rx.chakra.heading("Hello World!", size= "md", color="blue"), + rx.chakra.heading("Hello World!", size= "lg", color="green"), + rx.chakra.heading("Hello World!", size= "xl", color="blue"), + rx.chakra.heading("Hello World!", size= "2xl", color="red"), + rx.chakra.heading("Hello World!", size= "3xl", color="blue"), + rx.chakra.heading("Hello World!", size= "4xl", color="green"), +) +``` + +It can also be styled using regular CSS styles. + +```python demo +rx.chakra.heading("Hello World!", font_size="2em") +``` diff --git a/docs/library/chakra/typography/highlight.md b/docs/library/chakra/typography/highlight.md new file mode 100644 index 000000000..d37c43c3a --- /dev/null +++ b/docs/library/chakra/typography/highlight.md @@ -0,0 +1,20 @@ +--- +components: + - rx.chakra.Highlight +--- + +```python exec +import reflex as rx +``` + +# Highlight + +The highlight component take in a string and display some of the words as highlighted text. + +The words to highlight can be selected using the `query` prop. + +You can also customize how the hightlight will be rendered with the `styles` prop. + +```python demo +rx.chakra.highlight("Hello World, we have some highlight", query=['World','some'], styles={ 'px': '2', 'py': '1', 'rounded': 'full', 'bg': 'grey' }) +``` diff --git a/docs/library/chakra/typography/span.md b/docs/library/chakra/typography/span.md new file mode 100644 index 000000000..55108e4c8 --- /dev/null +++ b/docs/library/chakra/typography/span.md @@ -0,0 +1,21 @@ +--- +components: + - rx.chakra.span +--- + +```python exec +import reflex as rx +``` + +# Span + +The span component can be used to style inline text without creating a new line. + +```python demo +rx.chakra.box( + "Write some ", + rx.chakra.span("stylized ", color="red"), + rx.chakra.span("text ", color="blue"), + rx.chakra.span("using spans.", font_weight="bold") +) +``` diff --git a/docs/library/chakra/typography/text.md b/docs/library/chakra/typography/text.md new file mode 100644 index 000000000..1de0b95dd --- /dev/null +++ b/docs/library/chakra/typography/text.md @@ -0,0 +1,27 @@ +--- +components: + - rx.chakra.Text +--- + +```python exec +import reflex as rx +``` + +# Text + +The text component displays a paragraph of text. + +```python demo +rx.chakra.text("Hello World!", font_size="2em") +``` + +The text element can be visually modified using the `as_` prop. + +```python demo +rx.chakra.vstack( + rx.chakra.text("Hello World!", as_="i"), + rx.chakra.text("Hello World!", as_="s"), + rx.chakra.text("Hello World!", as_="mark"), + rx.chakra.text("Hello World!", as_="sub"), +) +``` diff --git a/docs/library/datadisplay/avatar.md b/docs/library/datadisplay/avatar.md new file mode 100644 index 000000000..a364edead --- /dev/null +++ b/docs/library/datadisplay/avatar.md @@ -0,0 +1,145 @@ +--- +components: + - rx.radix.avatar +Avatar: | + lambda **props: rx.hstack(rx.avatar(src="/logo.jpg", **props), rx.avatar(fallback="RX", **props), spacing="3") +--- +# Avatar + +```python exec +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +The Avatar component is used to represent a user, and display their profile pictures or fallback texts such as initials. + +## Basic Example + +To create an avatar component with an image, pass the image URL as the `src` prop. + +```python demo +rx.avatar(src="/logo.jpg") +``` + +To display a text such as initials, set the `fallback` prop without passing the `src` prop. + +```python demo +rx.avatar(fallback="RX") +``` + +## Styling + +```python eval +style_grid(component_used=rx.avatar, component_used_str="rx.avatar", variants=["solid", "soft"], fallback="RX") +``` + +### Size + +The `size` prop controls the size and spacing of the avatar. The acceptable size is from `"1"` to `"9"`, with `"3"` being the default. + +```python demo +rx.flex( + rx.avatar(src="/logo.jpg", fallback="RX", size="1"), + rx.avatar(src="/logo.jpg", fallback="RX", size="2"), + rx.avatar(src="/logo.jpg", fallback="RX", size="3"), + rx.avatar(src="/logo.jpg", fallback="RX"), + rx.avatar(src="/logo.jpg", fallback="RX", size="4"), + rx.avatar(src="/logo.jpg", fallback="RX", size="5"), + rx.avatar(src="/logo.jpg", fallback="RX", size="6"), + rx.avatar(src="/logo.jpg", fallback="RX", size="7"), + rx.avatar(src="/logo.jpg", fallback="RX", size="8"), + spacing="1", +) +``` + +### Variant + +The `variant` prop controls the visual style of the avatar fallback text. The variant can be `"solid"` or `"soft"`. The default is `"soft"`. + +```python demo +rx.flex( + rx.avatar(fallback="RX", variant="solid"), + rx.avatar(fallback="RX", variant="soft"), + rx.avatar(fallback="RX"), + spacing="2", +) +``` + +### Color Scheme + +The `color_scheme` prop sets a specific color to the fallback text, ignoring the global theme. + +```python demo +rx.flex( + rx.avatar(fallback="RX", color_scheme="indigo"), + rx.avatar(fallback="RX", color_scheme="cyan"), + rx.avatar(fallback="RX", color_scheme="orange"), + rx.avatar(fallback="RX", color_scheme="crimson"), + spacing="2", +) +``` + +### High Contrast + +The `high_contrast` prop increases color contrast of the fallback text with the background. + +```python demo +rx.grid( + rx.avatar(fallback="RX", variant="solid"), + rx.avatar(fallback="RX", variant="solid", high_contrast=True), + rx.avatar(fallback="RX", variant="soft"), + rx.avatar(fallback="RX", variant="soft", high_contrast=True), + rows="2", + spacing="2", + flow="column", +) +``` + +### Radius + +The `radius` prop sets specific radius value, ignoring the global theme. It can take values `"none" | "small" | "medium" | "large" | "full"`. + +```python demo +rx.grid( + rx.avatar(src="/logo.jpg", fallback="RX", radius="none"), + rx.avatar(fallback="RX", radius="none"), + rx.avatar(src="/logo.jpg", fallback="RX", radius="small"), + rx.avatar(fallback="RX", radius="small"), + rx.avatar(src="/logo.jpg", fallback="RX", radius="medium"), + rx.avatar(fallback="RX", radius="medium"), + rx.avatar(src="/logo.jpg", fallback="RX", radius="large"), + rx.avatar(fallback="RX", radius="large"), + rx.avatar(src="/logo.jpg", fallback="RX", radius="full"), + rx.avatar(fallback="RX", radius="full"), + rows="2", + spacing="2", + flow="column", +) +``` + +### Fallback + +The `fallback` prop indicates the rendered text when the `src` cannot be loaded. + +```python demo +rx.flex( + rx.avatar(fallback="RX"), + rx.avatar(fallback="PC"), + spacing="2", +) +``` + +## Final Example + +As part of a user profile page, the Avatar component is used to display the user's profile picture, with the fallback text showing the user's initials. Text components displays the user's full name and username handle and a Button component shows the edit profile button. + +```python demo +rx.flex( + rx.avatar(src="/logo.jpg", fallback="RU", size="9"), + rx.text("Reflex User", weight="bold", size="4"), + rx.text("@reflexuser", color_scheme="gray"), + rx.button("Edit Profile", color_scheme="indigo", variant="solid"), + direction="column", + spacing="1", +) +``` diff --git a/docs/library/datadisplay/badge.md b/docs/library/datadisplay/badge.md new file mode 100644 index 000000000..16a5aeda8 --- /dev/null +++ b/docs/library/datadisplay/badge.md @@ -0,0 +1,128 @@ +--- +components: + - rx.radix.badge + +Badge: | + lambda **props: rx.badge("Basic Badge", **props) +--- +# Badge + +```python exec +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +Badges are used to highlight an item's status for quick recognition. + +## Basic Example + +To create a badge component with only text inside, pass the text as an argument. + +```python demo +rx.badge("New") +``` + +## Styling + +```python eval +style_grid(component_used=rx.badge, component_used_str="rx.badge", variants=["solid", "soft", "surface", "outline"], components_passed="England!",) +``` + +### Size + +The `size` prop controls the size and padding of a badge. It can take values of `"1" | "2"`, with default being `"1"`. + +```python demo +rx.flex( + rx.badge("New"), + rx.badge("New", size="1"), + rx.badge("New", size="2"), + align="center", + spacing="2", +) +``` + +### Variant + +The `variant` prop controls the visual style of the badge. The supported variant types are `"solid" | "soft" | "surface" | "outline"`. The variant default is `"soft"`. + +```python demo +rx.flex( + rx.badge("New", variant="solid"), + rx.badge("New", variant="soft"), + rx.badge("New"), + rx.badge("New", variant="surface"), + rx.badge("New", variant="outline"), + spacing="2", +) +``` + +### Color Scheme + +The `color_scheme` prop sets a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.badge("New", color_scheme="indigo"), + rx.badge("New", color_scheme="cyan"), + rx.badge("New", color_scheme="orange"), + rx.badge("New", color_scheme="crimson"), + spacing="2", +) +``` + +### High Contrast + +The `high_contrast` prop increases color contrast of the fallback text with the background. + +```python demo +rx.flex( + rx.flex( + rx.badge("New", variant="solid"), + rx.badge("New", variant="soft"), + rx.badge("New", variant="surface"), + rx.badge("New", variant="outline"), + spacing="2", + ), + rx.flex( + rx.badge("New", variant="solid", high_contrast=True), + rx.badge("New", variant="soft", high_contrast=True), + rx.badge("New", variant="surface", high_contrast=True), + rx.badge("New", variant="outline", high_contrast=True), + spacing="2", + ), + direction="column", + spacing="2", +) +``` + +### Radius + +The `radius` prop sets specific radius value, ignoring the global theme. It can take values `"none" | "small" | "medium" | "large" | "full"`. + +```python demo +rx.flex( + rx.badge("New", radius="none"), + rx.badge("New", radius="small"), + rx.badge("New", radius="medium"), + rx.badge("New", radius="large"), + rx.badge("New", radius="full"), + spacing="3", +) +``` + +## Final Example + +A badge may contain more complex elements within it. This example uses a `flex` component to align an icon and the text correctly, using the `gap` prop to +ensure a comfortable spacing between the two. + +```python demo +rx.badge( + rx.flex( + rx.icon(tag="arrow_up"), + rx.text("8.8%"), + spacing="1", + ), + color_scheme="grass", +) +``` diff --git a/docs/library/datadisplay/callout-ll.md b/docs/library/datadisplay/callout-ll.md new file mode 100644 index 000000000..b3e251bfe --- /dev/null +++ b/docs/library/datadisplay/callout-ll.md @@ -0,0 +1,140 @@ +--- +components: + - rx.radix.callout.root + - rx.radix.callout.icon + - rx.radix.callout.text +--- + + +```python exec +import reflex as rx +``` + +# Callout + +A `callout` is a short message to attract user's attention. + +```python demo +rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), +) +``` + +The `callout` component is made up of a `callout.root`, which groups `callout.icon` and `callout.text` parts. This component is based on the `div` element and supports common margin props. + +The `callout.icon` provides width and height for the `icon` associated with the `callout`. This component is based on the `div` element. See the [**icon** component for all icons that are available.](/docs/library/radix/datadisplay/icon) + +The `callout.text` renders the callout text. This component is based on the `p` element. + +## As alert + +```python demo +rx.callout.root( + rx.callout.icon(rx.icon(tag="alert_triangle")), + rx.callout.text("Access denied. Please contact the network administrator to view this page."), + color_scheme="red", + role="alert", +) +``` + +## Style + +### Size + +Use the `size` prop to control the size. + +```python demo +rx.flex( + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + size="3", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + size="2", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + size="1", + ), + direction="column", + spacing="3", + align="start", +) +``` + +### Variant + +Use the `variant` prop to control the visual style. It is set to `soft` by default. + +```python demo +rx.flex( + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + variant="soft", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + variant="surface", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + variant="outline", + ), + direction="column", + spacing="3", +) +``` + +### Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + color_scheme="blue", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + color_scheme="green", + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + color_scheme="red", + ), + direction="column", + spacing="3", +) +``` + +### High Contrast + +Use the `high_contrast` prop to add additional contrast. + +```python demo +rx.flex( + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + ), + rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + high_contrast=True, + ), + direction="column", + spacing="3", +) +``` diff --git a/docs/library/datadisplay/callout.md b/docs/library/datadisplay/callout.md new file mode 100644 index 000000000..5f4602955 --- /dev/null +++ b/docs/library/datadisplay/callout.md @@ -0,0 +1,96 @@ +--- +components: + - rx.radix.callout + - rx.radix.callout.root + - rx.radix.callout.icon + - rx.radix.callout.text + +Callout: | + lambda **props: rx.callout("Basic Callout", icon="search", **props) + +CalloutRoot: | + lambda **props: rx.callout.root( + rx.callout.icon(rx.icon(tag="info")), + rx.callout.text("You will need admin privileges to install and access this application."), + **props + ) +--- + + +```python exec +import reflex as rx +``` + +# Callout + +A `callout` is a short message to attract user's attention. + +```python demo +rx.callout("You will need admin privileges to install and access this application.", icon="info") +``` + +The `icon` prop allows an icon to be passed to the `callout` component. See the [**icon** component for all icons that are available.](/docs/library/radix/datadisplay/icon) + +## As alert + +```python demo +rx.callout("Access denied. Please contact the network administrator to view this page.", icon="alert_triangle", color_scheme="red", role="alert") +``` + +## Style + +### Size + +Use the `size` prop to control the size. + +```python demo +rx.flex( + rx.callout("You will need admin privileges to install and access this application.", icon="info", size="3",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", size="2",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", size="1",), + direction="column", + spacing="3", + align="start", +) +``` + +### Variant + +Use the `variant` prop to control the visual style. It is set to `soft` by default. + +```python demo +rx.flex( + rx.callout("You will need admin privileges to install and access this application.", icon="info", variant="soft",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", variant="surface",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", variant="outline",), + direction="column", + spacing="3", +) +``` + +### Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.callout("You will need admin privileges to install and access this application.", icon="info", color_scheme="blue",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", color_scheme="green",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", color_scheme="red",), + direction="column", + spacing="3", +) +``` + +### High Contrast + +Use the `high_contrast` prop to add additional contrast. + +```python demo +rx.flex( + rx.callout("You will need admin privileges to install and access this application.", icon="info",), + rx.callout("You will need admin privileges to install and access this application.", icon="info", high_contrast=True,), + direction="column", + spacing="3", +) +``` diff --git a/docs/library/datadisplay/codeblock.md b/docs/library/datadisplay/codeblock.md new file mode 100644 index 000000000..dcef96e29 --- /dev/null +++ b/docs/library/datadisplay/codeblock.md @@ -0,0 +1,25 @@ +--- +components: + - rx.code_block +--- + +```python exec +import reflex as rx +``` + +# CodeBlock + +The CodeBlock component can be used to display code easily within a website. +Put in a multiline string with the correct spacing and specify and language to show the desired code. + +```python demo +rx.code_block( + """def fib(n): + if n <= 1: + return n + else: + return(fib(n-1) + fib(n-2))""", + language="python", + show_line_numbers=True, +) +``` diff --git a/docs/library/datadisplay/data_editor.md b/docs/library/datadisplay/data_editor.md new file mode 100644 index 000000000..fff39366f --- /dev/null +++ b/docs/library/datadisplay/data_editor.md @@ -0,0 +1,356 @@ +--- +components: + - rx.data_editor +--- + +# Data Editor + +A datagrid editor based on [Glide Data Grid](https://grid.glideapps.com/) + +```python exec +import reflex as rx +from pcweb.pages.docs import library +from typing import Any + +columns: list[dict[str, str]] = [ + { + "title":"Code", + "type": "str", + }, + { + "title":"Value", + "type": "int", + }, + { + "title":"Activated", + "type": "bool", + }, +] +data: list[list[Any]] = [ + ["A", 1, True], + ["B", 2, False], + ["C", 3, False], + ["D", 4, True], + ["E", 5, True], + ["F", 6, False], +] + +``` + +This component is introduced as an alternative to the [datatable]({library.datadisplay.datatable.path}) to support editing the displayed data. + +## Columns + +The columns definition should be a `list` of `dict`, each `dict` describing the associated columns. +Property of a column dict: + +- `title`: The text to display in the header of the column. +- `id`: An id for the column, if not defined, will default to a lower case of `title` +- `width`: The width of the column. +- `type`: The type of the columns, default to `"str"`. + +## Data + +The `data` props of `rx.data_editor` accept a `list` of `list`, where each `list` represent a row of data to display in the table. + +## Simple Example + +Here is a basic example of using the data_editor representing data with no interaction and no styling. Below we define the `columns` and the `data` which are taken in by the `rx.data_editor` component. When we define the `columns` we must define a `title` and a `type` for each column we create. The columns in the `data` must then match the defined `type` or errors will be thrown. + +```python demo box +rx.data_editor( + columns=columns, + data=data, +) +``` + +```python +columns: list[dict[str, str]] = [ + { + "title":"Code", + "type": "str", + }, + { + "title":"Value", + "type": "int", + }, + { + "title":"Activated", + "type": "bool", + }, +] +data: list[list[Any]] = [ + ["A", 1, True], + ["B", 2, False], + ["C", 3, False], + ["D", 4, True], + ["E", 5, True], + ["F", 6, False], +] +``` + +```python +rx.data_editor( + columns=columns, + data=data, +) +``` + +## Interactive Example + +```python exec +class DataEditorState_HP(rx.State): + + clicked_data: str = "Cell clicked: " + cols: list[Any] = [ + {"title": "Title", "type": "str"}, + { + "title": "Name", + "type": "str", + "group": "Data", + "width": 300, + }, + { + "title": "Birth", + "type": "str", + "id": "date", + "group": "Data", + "width": 150, + }, + { + "title": "Human", + "type": "bool", + "group": "Data", + "width": 80, + }, + { + "title": "House", + "type": "str", + "id": "date", + "group": "Data", + }, + { + "title": "Wand", + "type": "str", + "id": "date", + "group": "Data", + "width": 250, + }, + { + "title": "Patronus", + "type": "str", + "id": "date", + "group": "Data", + }, + { + "title": "Blood status", + "type": "str", + "id": "date", + "group": "Data", + "width": 200, + } + ] + + data = [ + ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"], + ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"], + ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], + ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], + ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], + ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], + ] + + def click_cell(self, pos): + col, row = pos + yield self.get_clicked_data(pos) + + + def get_clicked_data(self, pos) -> str: + self.clicked_data = f"Cell clicked: {pos}" + +``` + +Here we define a State, as shown below, that allows us to print the location of the cell as a heading when we click on it, using the `on_cell_clicked` `event trigger`. Check out all the other `event triggers` that you can use with datatable at the bottom of this page. We also define a `group` with a label `Data`. This groups all the columns with this `group` label under a larger group `Data` as seen in the table below. + +```python demo box +rx.heading(DataEditorState_HP.clicked_data) +``` + +```python demo box +rx.data_editor( + columns=DataEditorState_HP.cols, + data=DataEditorState_HP.data, + on_cell_clicked=DataEditorState_HP.click_cell, +) +``` + +```python +class DataEditorState_HP(rx.State): + + clicked_data: str = "Cell clicked: " + + cols: list[Any] = [ + { + "title": "Title", + "type": "str" + }, + { + "title": "Name", + "type": "str", + "group": "Data", + "width": 300, + }, + { + "title": "Birth", + "type": "str", + "group": "Data", + "width": 150, + }, + { + "title": "Human", + "type": "bool", + "group": "Data", + "width": 80, + }, + { + "title": "House", + "type": "str", + "group": "Data", + }, + { + "title": "Wand", + "type": "str", + "group": "Data", + "width": 250, + }, + { + "title": "Patronus", + "type": "str", + "group": "Data", + }, + { + "title": "Blood status", + "type": "str", + "group": "Data", + "width": 200, + } + ] + + data = [ + ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"], + ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"], + ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"], + ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"], + ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"], + ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"], + ] + + + def click_cell(self, pos): + col, row = pos + yield self.get_clicked_data(pos) + + + def get_clicked_data(self, pos) -> str: + self.clicked_data = f"Cell clicked: \{pos}" +``` + +```python +rx.data_editor( + columns=DataEditorState_HP.cols, + data=DataEditorState_HP.data, + on_cell_clicked=DataEditorState_HP.click_cell, +) +``` + +## Styling Example + +Now let's style our datatable to make it look more aesthetic and easier to use. We must first import `DataEditorTheme` and then we can start setting our style props as seen below in `dark_theme`. + +We then set these themes using `theme=DataEditorTheme(**dark_theme)`. On top of the styling we can also set some `props` to make some other aesthetic changes to our datatable. We have set the `row_height` to equal `50` so that the content is easier to read. We have also made the `smooth_scroll_x` and `smooth_scroll_y` equal `True` so that we can smoothly scroll along the columns and rows. Finally, we added `column_select=single`, where column select can take any of the following values `none`, `single` or `multiple`. + +```python exec +from reflex.components.datadisplay.dataeditor import DataEditorTheme +dark_theme = { + "accentColor": "#8c96ff", + "accentLight": "rgba(202, 206, 255, 0.253)", + "textDark": "#ffffff", + "textMedium": "#b8b8b8", + "textLight": "#a0a0a0", + "textBubble": "#ffffff", + "bgIconHeader": "#b8b8b8", + "fgIconHeader": "#000000", + "textHeader": "#a1a1a1", + "textHeaderSelected": "#000000", + "bgCell": "#16161b", + "bgCellMedium": "#202027", + "bgHeader": "#212121", + "bgHeaderHasFocus": "#474747", + "bgHeaderHovered": "#404040", + "bgBubble": "#212121", + "bgBubbleSelected": "#000000", + "bgSearchResult": "#423c24", + "borderColor": "rgba(225,225,225,0.2)", + "drilldownBorder": "rgba(225,225,225,0.4)", + "linkColor": "#4F5DFF", + "headerFontStyle": "bold 14px", + "baseFontStyle": "13px", + "fontFamily": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", +} +``` + +```python demo box +rx.data_editor( + columns=DataEditorState_HP.cols, + data=DataEditorState_HP.data, + row_height=80, + smooth_scroll_x=True, + smooth_scroll_y=True, + column_select="single", + theme=DataEditorTheme(**dark_theme), + height="30vh", +) +``` + +```python +from reflex.components.datadisplay.dataeditor import DataEditorTheme +dark_theme_snake_case = { + "accent_color": "#8c96ff", + "accent_light": "rgba(202, 206, 255, 0.253)", + "text_dark": "#ffffff", + "text_medium": "#b8b8b8", + "text_light": "#a0a0a0", + "text_bubble": "#ffffff", + "bg_icon_header": "#b8b8b8", + "fg_icon_header": "#000000", + "text_header": "#a1a1a1", + "text_header_selected": "#000000", + "bg_cell": "#16161b", + "bg_cell_medium": "#202027", + "bg_header": "#212121", + "bg_header_has_focus": "#474747", + "bg_header_hovered": "#404040", + "bg_bubble": "#212121", + "bg_bubble_selected": "#000000", + "bg_search_result": "#423c24", + "border_color": "rgba(225,225,225,0.2)", + "drilldown_border": "rgba(225,225,225,0.4)", + "link_color": "#4F5DFF", + "header_font_style": "bold 14px", + "base_font_style": "13px", + "font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif", +} +``` + +```python +rx.data_editor( + columns=DataEditorState_HP.cols, + data=DataEditorState_HP.data, + row_height=80, + smooth_scroll_x=True, + smooth_scroll_y=True, + column_select="single", + theme=DataEditorTheme(**dark_theme), + height="30vh", +) +``` diff --git a/docs/library/datadisplay/datatable.md b/docs/library/datadisplay/datatable.md new file mode 100644 index 000000000..325e372cd --- /dev/null +++ b/docs/library/datadisplay/datatable.md @@ -0,0 +1,67 @@ +--- +components: + - rx.data_table +--- + +```python exec +import reflex as rx +``` + +# DataTable + +The datatable component is a great way to display data in a table format. +You can pass in a pandas dataframe to the data prop to create the table. + +In this example we will read data from a csv file, convert it to a pandas dataframe and display it in a data_table. + +We will also add a search, pagination, sorting to the data_table to make it more accessible. + +```python demo box +rx.data_table( + data=[ + ["Avery Bradley", "6-2", 25.0], + ["Jae Crowder", "6-6", 25.0], + ["John Holland", "6-5", 27.0], + ["R.J. Hunter", "6-5", 22.0], + ["Jonas Jerebko", "6-10", 29.0], + ["Amir Johnson", "6-9", 29.0], + ["Jordan Mickey", "6-8", 21.0], + ["Kelly Olynyk", "7-0", 25.0], + ["Terry Rozier", "6-2", 22.0], + ["Marcus Smart", "6-4", 22.0], + ], + columns=["Name", "Height", "Age"], + pagination=True, + search=True, + sort=True, +) +``` + +```python +import pandas as pd +nba_data = pd.read_csv("https://media.geeksforgeeks.org/wp-content/uploads/nba.csv")""" +... +rx.data_table( + data = nba_data[["Name", "Height", "Age"]], + pagination= True, + search= True, + sort= True, +) +``` + +The example below shows how to create a data table from from a list. + +```python +class State(rx.State): + data: List = [ + ["Lionel", "Messi", "PSG"], + ["Christiano", "Ronaldo", "Al-Nasir"] + ] + columns: List[str] = ["First Name", "Last Name"] + +def index(): + return rx.data_table( + data=State.data, + columns=State.columns, + ) +``` diff --git a/docs/library/datadisplay/icon.md b/docs/library/datadisplay/icon.md new file mode 100644 index 000000000..8a8f13b60 --- /dev/null +++ b/docs/library/datadisplay/icon.md @@ -0,0 +1,131 @@ +--- +components: + - rx.lucide.Icon +--- + +```python exec +import reflex as rx +``` + +# Icon + +The Icon component is used to display an icon from a library of icons. This implementation is based on the [Lucide Icons](https://lucide.dev/icons) where you can find a list of all available icons. + + +## Basic Example + +To display an icon, specify the `tag` prop from the list of available icons. +Passing the tag as the first children is also supported and will be assigned to the `tag` prop. + +The `tag` is expected to be in `snake_case` format, but `kebab-case` is also supported to allow copy-paste from [https://lucide.dev/icons](https://lucide.dev/icons). + +```python demo +rx.flex( + rx.icon("calendar"), + rx.icon(tag="calendar"), + gap="2", +) +``` + +## Styling + +Icon from Lucide can be customized with the following props `stroke_width`, `size` and `color`. + +### Stroke Width + +```python demo +rx.flex( + rx.icon("moon", stroke_width=1), + rx.icon("moon", stroke_width=1.5), + rx.icon("moon", stroke_width=2), + rx.icon("moon", stroke_width=2.5), + gap="2" +) +``` + + +### Size + +```python demo +rx.flex( + rx.icon("zoom_in", size=15), + rx.icon("zoom_in", size=20), + rx.icon("zoom_in", size=25), + rx.icon("zoom_in", size=30), + align="center", + gap="2", +) +``` + +### Color + +Here is an example using basic colors in icons. + +```python demo +rx.flex( + rx.icon("zoom_in", size=18, color="indigo"), + rx.icon("zoom_in", size=18, color="cyan"), + rx.icon("zoom_in", size=18, color="orange"), + rx.icon("zoom_in", size=18, color="crimson"), + gap="2", +) +``` + +A radix color with a scale may also be specified using the `var()` token syntax seen below. + +```python demo +rx.flex( + rx.icon("zoom_in", size=18, color="var(--purple-1)"), + rx.icon("zoom_in", size=18, color="var(--purple-2)"), + rx.icon("zoom_in", size=18, color="var(--purple-3)"), + rx.icon("zoom_in", size=18, color="var(--purple-4)"), + rx.icon("zoom_in", size=18, color="var(--purple-5)"), + rx.icon("zoom_in", size=18, color="var(--purple-6)"), + rx.icon("zoom_in", size=18, color="var(--purple-7)"), + rx.icon("zoom_in", size=18, color="var(--purple-8)"), + rx.icon("zoom_in", size=18, color="var(--purple-9)"), + rx.icon("zoom_in", size=18, color="var(--purple-10)"), + rx.icon("zoom_in", size=18, color="var(--purple-11)"), + rx.icon("zoom_in", size=18, color="var(--purple-12)"), + gap="2", +) +``` + +Here is another example using the `accent` color with scales. The `accent` is the most dominant color in your theme. + +```python demo +rx.flex( + rx.icon("zoom_in", size=18, color="var(--accent-1)"), + rx.icon("zoom_in", size=18, color="var(--accent-2)"), + rx.icon("zoom_in", size=18, color="var(--accent-3)"), + rx.icon("zoom_in", size=18, color="var(--accent-4)"), + rx.icon("zoom_in", size=18, color="var(--accent-5)"), + rx.icon("zoom_in", size=18, color="var(--accent-6)"), + rx.icon("zoom_in", size=18, color="var(--accent-7)"), + rx.icon("zoom_in", size=18, color="var(--accent-8)"), + rx.icon("zoom_in", size=18, color="var(--accent-9)"), + rx.icon("zoom_in", size=18, color="var(--accent-10)"), + rx.icon("zoom_in", size=18, color="var(--accent-11)"), + rx.icon("zoom_in", size=18, color="var(--accent-12)"), + gap="2", +) +``` + +## Final Example + +Icons can be used as child components of many other components. For example, adding a magnifying glass icon to a search bar. + +```python demo +rx.badge( + rx.flex( + rx.icon("search", size=18), + rx.text("Search documentation...", size="3", weight="medium"), + direction="row", + gap="1", + align="center", + ), + size="2", + radius="full", + color_scheme="gray", +) +``` \ No newline at end of file diff --git a/docs/library/datadisplay/list.md b/docs/library/datadisplay/list.md new file mode 100644 index 000000000..ab388d127 --- /dev/null +++ b/docs/library/datadisplay/list.md @@ -0,0 +1,62 @@ +--- +components: + - rx.radix.themes.list_item + - rx.radix.themes.ordered_list + - rx.radix.themes.unordered_list +--- + +```python exec +import reflex as rx +``` + +# List + +A `list` is a component that is used to display a list of items, stacked vertically by default. A `list` which can be either `ordered` or `unordered`. It is based on the `flex` component and therefore inherits all of its props. + +An `unordered_list` has bullet points to display the list items. The `list_item` component + +```python demo +rx.unordered_list( + rx.list_item("Example 1"), + rx.list_item("Example 2"), + rx.list_item("Example 3"), +) +``` + +An `ordered_list` has numbers to display the list items. + +```python demo +rx.ordered_list( + rx.list_item("Example 1"), + rx.list_item("Example 2"), + rx.list_item("Example 3"), +) +``` + +An `unordered_list` or an `ordered_list` can have no bullet points or numbers by setting the `list_style_type` prop to `none`. + +```python demo +rx.unordered_list( + rx.list_item("Example 1"), + rx.list_item("Example 2"), + rx.list_item("Example 3"), + list_style_type="none", +) +``` + +Lists can also be used with icons. + +```python demo +rx.unordered_list( + rx.list_item( + rx.icon("check_circle", color="green", style={"display": "inline"}), "Allowed", + ), + rx.list_item( + rx.icon("x-octagon", color="red", style={"display": "inline"}), "Not", + ), + rx.list_item( + rx.icon("settings", color="grey", style={"display": "inline"}), "Settings" + ), + list_style_type="none", +) +``` diff --git a/docs/library/datadisplay/progress-ll.md b/docs/library/datadisplay/progress-ll.md new file mode 100644 index 000000000..04c9f696c --- /dev/null +++ b/docs/library/datadisplay/progress-ll.md @@ -0,0 +1,55 @@ +--- +components: + - rx.radix.primitives.progress.root + - rx.radix.primitives.progress.indicator + +Progress: | + lambda **props: rx.progress(value=50, width="100%", **props) + +ProgressIndicator: | + lambda **props: rx.progress.root( + rx.progress.indicator(value=50, **props), + width="100%", + ) +--- + +```python exec +import reflex as rx +import asyncio + +class ProgressState(rx.State): + value: int = 0 + + @rx.background + async def start_progress(self): + async with self: + self.value = 0 + while self.value < 100: + await asyncio.sleep(0.1) + async with self: + self.value += 1 +``` + +# Progress + +Progress is used to display the progress status for a task that takes a long time or consists of several steps. + +## Basic Example + +`rx.progress` expect the `value` prop to set the progress value. + +```python demo +rx.vstack( + rx.progress(value=0, width="100%"), + rx.progress(value=50, width="100%"), + rx.progress(value=100, width="100%"), + gap="1em", + min_width=["10em", "20em"], +) +``` + +For a dynamic progress, you can assign a state variable to the `value` prop instead of a constant value. + +```python demo +rx.hstack(rx.progress(value=ProgressState.value, width="100%"), rx.button("Start", on_click=ProgressState.start_progress)) +``` diff --git a/docs/library/datadisplay/progress.md b/docs/library/datadisplay/progress.md new file mode 100644 index 000000000..b93bba9e2 --- /dev/null +++ b/docs/library/datadisplay/progress.md @@ -0,0 +1,52 @@ +--- +components: + - rx.radix.primitives.progress.root + +Progress: | + lambda **props: rx.progress(value=50, width="100%", **props) +--- + +```python exec +import reflex as rx +import asyncio + +class ProgressState(rx.State): + value: int = 0 + + @rx.background + async def start_progress(self): + async with self: + self.value = 0 + while self.value < 100: + await asyncio.sleep(0.1) + async with self: + self.value += 1 +``` + +# Progress + +Progress is used to display the progress status for a task that takes a long time or consists of several steps. + +## Basic Example + +`rx.progress` expect the `value` prop to set the progress value. + +```python demo +rx.vstack( + rx.progress(value=0, width="100%"), + rx.progress(value=50, width="100%"), + rx.progress(value=100, width="100%"), + gap="1em", + min_width=["10em", "20em"], +) +``` + +For a dynamic progress, you can assign a state variable to the `value` prop instead of a constant value. + +```python demo +rx.hstack( + rx.progress(value=ProgressState.value, width="100%"), + rx.button("Start", on_click=ProgressState.start_progress), + width="10em" +) +``` diff --git a/docs/library/datadisplay/scroll_area.md b/docs/library/datadisplay/scroll_area.md new file mode 100644 index 000000000..eb4a5f3a7 --- /dev/null +++ b/docs/library/datadisplay/scroll_area.md @@ -0,0 +1,239 @@ +--- +components: + - rx.radix.scroll_area + +ScrollArea: | + lambda **props: rx.radix.themes.scroll_area( + rx.radix.themes.flex( + rx.radix.themes.text( + """Three fundamental aspects of typography are legibility, readability, and aesthetics. Although in a non-technical sense "legible" and "readable"are often used synonymously, typographically they are separate but related concepts.""", + size="5", + ), + rx.radix.themes.text( + """Legibility describes how easily individual characters can be distinguished from one another. It is described by Walter Tracy as "the quality of being decipherable and recognisable". For instance, if a "b" and an "h", or a "3" and an "8", are difficult to distinguish at small sizes, this is a problem of legibility.""", + size="5", + ), + direction="column", + spacing="4", + height="100px", + width="50%", + ), + **props + ) + +--- + + +```python exec +import random +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +# Scroll Area + +Custom styled, cross-browser scrollable area using native functionality. + +## Basic Example + +```python demo +rx.scroll_area( + rx.flex( + rx.text( + """Three fundamental aspects of typography are legibility, readability, and + aesthetics. Although in a non-technical sense “legible” and “readable” + are often used synonymously, typographically they are separate but + related concepts.""", + ), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as “the + quality of being decipherable and recognisable”. For instance, if a “b” + and an “h”, or a “3” and an “8”, are difficult to distinguish at small + sizes, this is a problem of legibility.""", + ), + rx.text( + """Typographers are concerned with legibility insofar as it is their job to + select the correct font to use. Brush Script is an example of a font + containing many characters that might be difficult to distinguish. The + selection of cases influences the legibility of typography because using + only uppercase letters (all-caps) reduces legibility.""", + ), + direction="column", + spacing="4", + ), + type="always", + scrollbars="vertical", + style={"height": 180}, + +) + +``` + +## Control the scrollable axes + +Use the `scrollbars` prop to limit scrollable axes. This prop can take values `"vertical" | "horizontal" | "both"`. + +```python demo +rx.grid( + rx.scroll_area( + rx.flex( + rx.text( + """Three fundamental aspects of typography are legibility, readability, and + aesthetics. Although in a non-technical sense "legible" and "readable" + are often used synonymously, typographically they are separate but + related concepts.""", + size="2", trim="both", + ), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", padding_right="48px", direction="column", spacing="4", + ), + type="always", + scrollbars="vertical", + style={"height": 150}, + ), + rx.scroll_area( + rx.flex( + rx.text( + """Three fundamental aspects of typography are legibility, readability, and + aesthetics. Although in a non-technical sense "legible" and "readable" + are often used synonymously, typographically they are separate but + related concepts.""", + size="2", trim="both", + ), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", spacing="4", style={"width": 700}, + ), + type="always", + scrollbars="horizontal", + style={"height": 150}, + ), + rx.scroll_area( + rx.flex( + rx.text( + """Three fundamental aspects of typography are legibility, readability, and + aesthetics. Although in a non-technical sense "legible" and "readable" + are often used synonymously, typographically they are separate but + related concepts.""", + size="2", trim="both", + ), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", spacing="4", style={"width": 400}, + ), + type="always", + scrollbars="both", + style={"height": 150}, + ), + columns="3", + spacing="2", +) +``` + +## Setting the type of the Scrollbars + +The `type` prop describes the nature of scrollbar visibility. + +`auto` means that scrollbars are visible when content is overflowing on the corresponding orientation. + +`always` means that scrollbars are always visible regardless of whether the content is overflowing. + +`scroll` means that scrollbars are visible when the user is scrolling along its corresponding orientation. + +`hover` when the user is scrolling along its corresponding orientation and when the user is hovering over the scroll area. + +```python demo +rx.grid( + rx.scroll_area( + rx.flex( + rx.text("type = 'auto'", weight="bold"), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", direction="column", spacing="4", + ), + type="auto", + scrollbars="vertical", + style={"height": 150}, + ), + rx.scroll_area( + rx.flex( + rx.text("type = 'always'", weight="bold"), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", direction="column", spacing="4", + ), + type="always", + scrollbars="vertical", + style={"height": 150}, + ), + rx.scroll_area( + rx.flex( + rx.text("type = 'scroll'", weight="bold"), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", direction="column", spacing="4", + ), + type="scroll", + scrollbars="vertical", + style={"height": 150}, + ), + rx.scroll_area( + rx.flex( + rx.text("type = 'hover'", weight="bold"), + rx.text( + """Legibility describes how easily individual characters can be + distinguished from one another. It is described by Walter Tracy as "the + quality of being decipherable and recognisable". For instance, if a "b" + and an "h", or a "3" and an "8", are difficult to distinguish at small + sizes, this is a problem of legibility.""", + size="2", trim="both", + ), + padding="8px", direction="column", spacing="4", + ), + type="hover", + scrollbars="vertical", + style={"height": 150}, + ), + columns="4", + spacing="2", +) + +``` diff --git a/docs/library/datadisplay/table.md b/docs/library/datadisplay/table.md new file mode 100644 index 000000000..511174d74 --- /dev/null +++ b/docs/library/datadisplay/table.md @@ -0,0 +1,218 @@ +--- +components: + - rx.table.root + - rx.table.header + - rx.table.row + - rx.table.column_header_cell + - rx.table.body + - rx.table.cell + - rx.table.row_header_cell + +only_low_level: + - True + +TableRoot: | + lambda **props: rx.radix.themes.table.root( + rx.radix.themes.table.header( + rx.radix.themes.table.row( + rx.radix.themes.table.column_header_cell("Full Name"), + rx.radix.themes.table.column_header_cell("Email"), + rx.radix.themes.table.column_header_cell("Group"), + ), + ), + rx.radix.themes.table.body( + rx.radix.themes.table.row( + rx.radix.themes.table.row_header_cell("Danilo Rosa"), + rx.radix.themes.table.cell("danilo@example.com"), + rx.radix.themes.table.cell("Developer"), + ), + rx.radix.themes.table.row( + rx.radix.themes.table.row_header_cell("Zahra Ambessa"), + rx.radix.themes.table.cell("zahra@example.com"), + rx.radix.themes.table.cell("Admin"), + ), + ), + width="80%", + **props, + ) + +TableRow: | + lambda **props: rx.radix.themes.table.root( + rx.radix.themes.table.header( + rx.radix.themes.table.row( + rx.radix.themes.table.column_header_cell("Full Name"), + rx.radix.themes.table.column_header_cell("Email"), + rx.radix.themes.table.column_header_cell("Group"), + **props, + ), + ), + rx.radix.themes.table.body( + rx.radix.themes.table.row( + rx.radix.themes.table.row_header_cell("Danilo Rosa"), + rx.radix.themes.table.cell(rx.radix.themes.text("danilo@example.com", as_="p"), rx.radix.themes.text("danilo@yahoo.com", as_="p"), rx.radix.themes.text("danilo@gmail.com", as_="p"),), + rx.radix.themes.table.cell("Developer"), + **props, + ), + rx.radix.themes.table.row( + rx.radix.themes.table.row_header_cell("Zahra Ambessa"), + rx.radix.themes.table.cell("zahra@example.com"), + rx.radix.themes.table.cell("Admin"), + **props, + ), + ), + width="80%", + ) + +TableColumnHeaderCell: | + lambda **props: rx.radix.themes.table.root( + rx.radix.themes.table.header( + rx.radix.themes.table.row( + rx.radix.themes.table.column_header_cell("Full Name", **props,), + rx.radix.themes.table.column_header_cell("Email", **props,), + rx.radix.themes.table.column_header_cell("Group", **props,), + ), + ), + rx.radix.themes.table.body( + rx.radix.themes.table.row( + rx.radix.themes.table.row_header_cell("Danilo Rosa"), + rx.radix.themes.table.cell("danilo@example.com"), + rx.radix.themes.table.cell("Developer"), + ), + rx.radix.themes.table.row( + rx.radix.themes.table.row_header_cell("Zahra Ambessa"), + rx.radix.themes.table.cell("zahra@example.com"), + rx.radix.themes.table.cell("Admin"), + ), + ), + width="80%", + ) + +TableCell: | + lambda **props: rx.radix.themes.table.root( + rx.radix.themes.table.header( + rx.radix.themes.table.row( + rx.radix.themes.table.column_header_cell("Full Name"), + rx.radix.themes.table.column_header_cell("Email"), + rx.radix.themes.table.column_header_cell("Group"), + ), + ), + rx.radix.themes.table.body( + rx.radix.themes.table.row( + rx.radix.themes.table.row_header_cell("Danilo Rosa"), + rx.radix.themes.table.cell("danilo@example.com", **props,), + rx.radix.themes.table.cell("Developer", **props,), + ), + rx.radix.themes.table.row( + rx.radix.themes.table.row_header_cell("Zahra Ambessa"), + rx.radix.themes.table.cell("zahra@example.com", **props,), + rx.radix.themes.table.cell("Admin", **props,), + ), + ), + width="80%", + ) + +TableRowHeaderCell: | + lambda **props: rx.radix.themes.table.root( + rx.radix.themes.table.header( + rx.radix.themes.table.row( + rx.radix.themes.table.column_header_cell("Full Name"), + rx.radix.themes.table.column_header_cell("Email"), + rx.radix.themes.table.column_header_cell("Group"), + ), + ), + rx.radix.themes.table.body( + rx.radix.themes.table.row( + rx.radix.themes.table.row_header_cell("Danilo Rosa", **props,), + rx.radix.themes.table.cell("danilo@example.com"), + rx.radix.themes.table.cell("Developer"), + ), + rx.radix.themes.table.row( + rx.radix.themes.table.row_header_cell("Zahra Ambessa", **props,), + rx.radix.themes.table.cell("zahra@example.com"), + rx.radix.themes.table.cell("Admin"), + ), + ), + width="80%", + ) +--- + + +```python exec +import reflex as rx +``` + +# Table + +A semantic table for presenting tabular data. + +## Basic Example + +```python demo +rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Sousa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + ),rx.table.row( + rx.table.row_header_cell("Jasper Eriksson"), + rx.table.cell("jasper@example.com"), + rx.table.cell("Developer"), + ), + ), +) +``` + +## Real World Example + +```python demo +rx.flex( + rx.heading("Your Team"), + rx.text("Invite and manage your team members"), + rx.flex( + rx.input(placeholder="Email Address"), + rx.button("Invite"), + justify="center", + spacing="2", + ), + rx.table.root( + rx.table.body( + rx.table.row( + rx.table.cell(rx.avatar(fallback="DS")), + rx.table.row_header_cell(rx.link("Danilo Sousa")), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + align="center", + ), + rx.table.row( + rx.table.cell(rx.avatar(fallback="ZA")), + rx.table.row_header_cell(rx.link("Zahra Ambessa")), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + align="center", + ), + rx.table.row( + rx.table.cell(rx.avatar(fallback="JE")), + rx.table.row_header_cell(rx.link("Jasper Eriksson")), + rx.table.cell("jasper@example.com"), + rx.table.cell("Developer"), + align="center", + ), + ), + ), + direction="column", + spacing="2", +) +``` diff --git a/docs/library/disclosure/accordion.md b/docs/library/disclosure/accordion.md new file mode 100644 index 000000000..7e9a2f412 --- /dev/null +++ b/docs/library/disclosure/accordion.md @@ -0,0 +1,373 @@ +--- +components: + - rx.radix.accordion.root + - rx.radix.accordion.item + +AccordionRoot: | + lambda **props: rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + width="300px", + **props, + ) + + +AccordionItem: | + lambda **props: rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em", **props,), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em", **props, + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em", **props,), + width="300px", + ) + +--- + +```python exec +import reflex as rx +``` + +# Accordion + +An accordion is a vertically stacked set of interactive headings that each reveal an associated section of content. +The accordion component is made up of `accordion`, which is the root of the component and takes in an `accordion.item`, +which contains all the contents of the collapsible section. + +## Basic Example + +```python demo +rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + width="300px", +) +``` + +## Styling + +### Type + +We use the `type` prop to determine whether multiple items can be opened at once. The allowed values for this prop are +`single` and `multiple` where `single` will only open one item at a time. The default value for this prop is `single`. + +```python demo +rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + width="300px", + type="multiple", +) +``` + +### Default Value + +We use the `default_value` prop to specify which item should open by default. The value for this prop should be any of the +unique values set by an `accordion.item`. + +```python demo +rx.flex( + rx.accordion.root( + rx.accordion.item( + header="First Item", + content="The first accordion item's content", + font_size="3em", + value="item_1", + ), + rx.accordion.item( + header="Second Item", + content="The second accordion item's content", + font_size="3em", + value="item_2", + ), + rx.accordion.item( + header="Third item", + content="The third accordion item's content", + font_size="3em", + value="item_3", + ), + width="300px", + default_value="item_2", + ), + direction="row", + spacing="2" +) +``` + +### Collapsible + +We use the `collapsible` prop to allow all items to close. If set to `False`, an opened item cannot be closed. + +```python demo +rx.flex( + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item(header="Second Item", content="The second accordion item's content", font_size="3em"), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + width="300px", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item(header="Second Item", content="The second accordion item's content", font_size="3em"), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=False, + width="300px", + ), + direction="row", + spacing="2" +) +``` + +### Disable + +We use the `disabled` prop to prevent interaction with the accordion and all its items. + +```python demo +rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item(header="Second Item", content="The second accordion item's content", font_size="3em"), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + width="300px", + disabled=True, +) +``` + +### Orientation + +We use `orientation` prop to set the orientation of the accordion to `vertical` or `horizontal`. By default, the orientation +will be set to `vertical`. Note that, the orientation prop wont change the visual orientation but the +functional orientation of the accordion. This means that for vertical orientation, the up and down arrow keys moves focus between the next or previous item, +while for horizontal orientation, the left or right arrow keys moves focus between items. + +```python demo +rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + width="300px", + orientation="vertical", +) +``` + +```python demo +rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + width="300px", + orientation="horizontal", +) +``` + +### Variant + +```python demo +rx.flex( + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + variant="classic", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + variant="soft", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + variant="outline", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + variant="surface", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + variant="ghost", + ), + direction="row", + spacing="2" + +) +``` + +### Color Scheme + +We use the `color_scheme` prop to assign a specific color to the accordion background, ignoring the global theme. + +```python demo +rx.flex( + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + width="300px", + color_scheme="grass", + ), + rx.accordion.root( + rx.accordion.item(header="First Item", content="The first accordion item's content", font_size="3em"), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + width="300px", + color_scheme="green", + ), + direction="row", + spacing="2" +) +``` + +### Value + +We use the `value` prop to specify the controlled value of the accordion item that we want to activate. +This property should be used in conjunction with the `on_value_change` event argument. + +```python demo exec +class AccordionState(rx.State): + """The app state.""" + + value: str = "item_1" + item_selected: str + + def change_value(self, value): + self.value = value + self.item_selected = f"{value} selected" + + +def index() -> rx.Component: + return rx.theme( + rx.container( + rx.text(AccordionState.item_selected), + rx.flex( + rx.accordion.root( + rx.accordion.item( + header="Is it accessible?", + content=rx.button("Test button"), + font_size="3em", + value="item_1", + ), + rx.accordion.item( + header="Is it unstyled?", + content="Yes. It's unstyled by default, giving you freedom over the look and feel.", + value="item_2", + ), + rx.accordion.item( + header="Is it finished?", + content="It's still in beta, but it's ready to use in production.", + value="item_3", + ), + collapsible=True, + width="300px", + value=AccordionState.value, + on_value_change=lambda value: AccordionState.change_value(value), + ), + direction="column", + spacing="2", + ), + padding="2em", + font_size="2em", + text_align="center", + ) + ) + + +``` + +## AccordionItem + +The accordion item contains all the parts of a collapsible section. + +## Styling + +### Value + +```python demo +rx.accordion.root( + rx.accordion.item( + header="First Item", + content="The first accordion item's content", + font_size="3em", + value="item_1", + ), + rx.accordion.item( + header="Second Item", + content="The second accordion item's content", + font_size="3em", + value="item_2", + ), + rx.accordion.item( + header="Third item", + content="The third accordion item's content", + font_size="3em", + value="item_3", + ), + collapsible=True, + width="300px", +) +``` + +### Disable + +```python demo +rx.accordion.root( + rx.accordion.item( + header="First Item", + content="The first accordion item's content", + font_size="3em", + disabled=True, + ), + rx.accordion.item( + header="Second Item", content="The second accordion item's content", font_size="3em" + ), + rx.accordion.item(header="Third item", content="The third accordion item's content", font_size="3em"), + collapsible=True, + width="300px", + color_scheme="blue", +) +``` diff --git a/docs/library/disclosure/tabs.md b/docs/library/disclosure/tabs.md new file mode 100644 index 000000000..591720723 --- /dev/null +++ b/docs/library/disclosure/tabs.md @@ -0,0 +1,323 @@ +--- +components: + - rx.radix.tabs.root + - rx.radix.tabs.list + - rx.radix.tabs.trigger + - rx.radix.tabs.content + +only_low_level: + - True + +TabsRoot: | + lambda **props: rx.radix.themes.tabs.root( + rx.radix.themes.tabs.list( + rx.radix.themes.tabs.trigger("Account", value="account"), + rx.radix.themes.tabs.trigger("Documents", value="documents"), + rx.radix.themes.tabs.trigger("Settings", value="settings"), + ), + rx.radix.themes.box( + rx.radix.themes.tabs.content( + rx.radix.themes.text("Make changes to your account"), + value="account", + ), + rx.radix.themes.tabs.content( + rx.radix.themes.text("Update your documents"), + value="documents", + ), + rx.radix.themes.tabs.content( + rx.radix.themes.text("Edit your personal profile"), + value="settings", + ), + ), + **props, + ) + +TabsList: | + lambda **props: rx.radix.themes.tabs.root( + rx.radix.themes.tabs.list( + rx.radix.themes.tabs.trigger("Account", value="account"), + rx.radix.themes.tabs.trigger("Documents", value="documents"), + rx.radix.themes.tabs.trigger("Settings", value="settings"), + **props, + ), + rx.radix.themes.box( + rx.radix.themes.tabs.content( + rx.radix.themes.text("Make changes to your account"), + value="account", + ), + rx.radix.themes.tabs.content( + rx.radix.themes.text("Update your documents"), + value="documents", + ), + rx.radix.themes.tabs.content( + rx.radix.themes.text("Edit your personal profile"), + value="settings", + ), + ), + ) + +TabsTrigger: | + lambda **props: rx.radix.themes.tabs.root( + rx.radix.themes.tabs.list( + rx.radix.themes.tabs.trigger("Account", value="account", **props,), + rx.radix.themes.tabs.trigger("Documents", value="documents"), + rx.radix.themes.tabs.trigger("Settings", value="settings"), + ), + rx.radix.themes.box( + rx.radix.themes.tabs.content( + rx.radix.themes.text("Make changes to your account"), + value="account", + ), + rx.radix.themes.tabs.content( + rx.radix.themes.text("Update your documents"), + value="documents", + ), + rx.radix.themes.tabs.content( + rx.radix.themes.text("Edit your personal profile"), + value="settings", + ), + ), + ) + +TabsContent: | + lambda **props: rx.radix.themes.tabs.root( + rx.radix.themes.tabs.list( + rx.radix.themes.tabs.trigger("Account", value="account"), + rx.radix.themes.tabs.trigger("Documents", value="documents"), + rx.radix.themes.tabs.trigger("Settings", value="settings"), + ), + rx.radix.themes.box( + rx.radix.themes.tabs.content( + rx.radix.themes.text("Make changes to your account"), + value="account", + **props, + ), + rx.radix.themes.tabs.content( + rx.radix.themes.text("Update your documents"), + value="documents", + **props, + ), + rx.radix.themes.tabs.content( + rx.radix.themes.text("Edit your personal profile"), + value="settings", + **props, + ), + ), + ) +--- + + +```python exec +import reflex as rx +``` + +# Tabs + +Tabs are a set of layered sections of content—known as tab panels that are displayed one at a time. +They facilitate the organization and navigation between sets of content that share a connection and exist at a similar level of hierarchy. + +## Basic Example + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2") + ), + rx.tabs.content( + rx.text("item on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("item on tab 2"), + value="tab2", + ), +) + +``` + +The `tabs` component is made up of a `rx.tabs.root` which groups `rx.tabs.list` and `rx.tabs.content` parts. + +## Styling + +### Default value + +We use the `default_value` prop to set a default active tab, this will select the specified tab by default. + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2") + ), + rx.tabs.content( + rx.text("item on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("item on tab 2"), + value="tab2", + ), + default_value="tab2", +) +``` + +### Orientation + +We use `orientation` prop to set the orientation of the tabs component to `vertical` or `horizontal`. By default, the orientation +will be set to `horizontal`. Note that, the orientation prop wont change the visual orientation but the +functional orientation. This means for vertical orientation, the up and down arrow keys moves focus between the next or previous tab, +while for horizontal orientation, the left and right arrow keys moves focus between tabs. + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2") + ), + rx.tabs.content( + rx.text("item on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("item on tab 2"), + value="tab2", + ), + default_value="tab1", + orientation="vertical", +) +``` + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2") + ), + rx.tabs.content( + rx.text("item on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("item on tab 2"), + value="tab2", + ), + default_value="tab1", + orientation="horizontal", +) +``` + +### Value + +We use the `value` prop to specify the controlled value of the tab that we want to activate. This property should be used in conjunction with the `on_change` event argument. + +```python demo exec +class TabsState(rx.State): + """The app state.""" + + value = "tab1" + tab_selected = "" + + def change_value(self, val): + self.tab_selected = f"{val} clicked!" + self.value = val + + +def index() -> rx.Component: + return rx.theme( + rx.container( + rx.flex( + rx.text(f"{TabsState.value} clicked !"), + rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2"), + ), + rx.tabs.content( + rx.text("items on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("items on tab 2"), + value="tab2", + ), + default_value="tab1", + value=TabsState.value, + on_change=lambda x: TabsState.change_value(x), + ), + direction="column", + spacing="2", + ), + padding="2em", + font_size="2em", + text_align="center", + ) + ) +``` + +## Tablist + +The Tablist is used to list the respective tabs to the tab component + +## Tab Trigger + +This is the button that activates the tab's associated content. It is typically used in the `Tablist` + +## Styling + +### Value + +We use the `value` prop to assign a unique value that associates the trigger with content. + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2"), + rx.tabs.trigger("Tab 3", value="tab3") + ), +) +``` + +### Disable + +We use the `disabled` prop to disable the tab. + +```python demo +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2"), + rx.tabs.trigger("Tab 3", value="tab3", disabled=True) + ), +) +``` + +## Tabs Content + +Contains the content associated with each trigger. + +## Styling + +### Value + +We use the `value` prop to assign a unique value that associates the content with a trigger. + +```python +rx.tabs.root( + rx.tabs.list( + rx.tabs.trigger("Tab 1", value="tab1"), + rx.tabs.trigger("Tab 2", value="tab2") + ), + rx.tabs.content( + rx.text("item on tab 1"), + value="tab1", + ), + rx.tabs.content( + rx.text("item on tab 2"), + value="tab2", + ), + default_value="tab1", + orientation="vertical", +) +``` diff --git a/docs/library/forms/button.md b/docs/library/forms/button.md new file mode 100644 index 000000000..1c57e3287 --- /dev/null +++ b/docs/library/forms/button.md @@ -0,0 +1,85 @@ +--- +components: + - rx.radix.button + +Button: | + lambda **props: rx.radix.themes.button("Basic Button", **props) +--- + + +```python exec +import reflex as rx +``` + +# Button + +Buttons are essential elements in your application's user interface that users can click to trigger events. This component uses Radix's [button](https://radix-ui.com/primitives/docs/components/button) component. + +## Basic Example + +```python demo +rx.button("Click me") +``` + +### With Icon + +```python demo +rx.button( + rx.icon(tag="heart"), + "Like", + color_scheme="red", +) +``` + +## Props + +### Disabled + +The `disabled` prop disables the button, by default it is `False`. A disabled button does not respond to user interactions such as click and cannot be focused. + +```python demo +rx.flex( + rx.button("Enabled"), + rx.button("Disabled", disabled=True), + spacing="2", +) +``` + +## Triggers + +### On Click + +The `on_click` trigger is called when the button is clicked. + +```python demo +rx.button("Click me", on_click=rx.window_alert("Clicked!")) +``` + +## Real World Example + +```python demo exec +class CountState(rx.State): + count: int = 0 + + def increment(self): + self.count += 1 + + def decrement(self): + self.count -= 1 + +def counter(): + return rx.flex( + rx.button( + "Decrement", + color_scheme="red", + on_click=CountState.decrement, + ), + rx.heading(CountState.count), + rx.button( + "Increment", + color_scheme="grass", + on_click=CountState.increment, + ), + spacing="3", + ) +``` diff --git a/docs/library/forms/checkbox.md b/docs/library/forms/checkbox.md new file mode 100644 index 000000000..cb871b85d --- /dev/null +++ b/docs/library/forms/checkbox.md @@ -0,0 +1,84 @@ +--- +components: + - rx.radix.checkbox + +HighLevelCheckbox: | + lambda **props: rx.radix.themes.checkbox("Basic Checkbox", **props) +--- + +```python exec +import reflex as rx +``` + +# Checkbox + +Checkboxes allow users to select one or more items from a set. + +## Basic example + +```python demo +rx.checkbox() +``` + +The `checkbox` component takes a `text` prop, which is the text label associated with the checkbox. + +The `default_checked` prop defines whether the `checkbox` is checked by default. + +The `gap` prop determines the space between the `checkbox` and the `text` label. + +```python demo +rx.checkbox("Agree to Terms and Conditios", default_checked=True, spacing="2") + +``` + +The `size` prop determines the size of the `checkbox` and the associated `text` label. + +```python demo +rx.checkbox("Agree to Terms and Conditios", size="3") +``` + +### Disabled + +The `disabled` prop disables the `checkbox`, by default it is `False`. A disabled `checkbox` does not respond to user interactions such as click and cannot be focused. + +```python demo +rx.hstack( + rx.checkbox(), + rx.checkbox(disabled=True), +) +``` + +## Triggers + +### OnChange + +The `on_change` trigger is called when the `checkbox` is clicked. + +```python demo +rx.checkbox("Agree to Terms and Conditios", default_checked=True, on_change=rx.window_alert("Checked!")) +``` + +The `checkbox` can also take other styling props such as `color_scheme` and `variant`. + +```python demo +rx.checkbox("Agree to Terms and Conditios", size="3", color_scheme="red", variant="soft") +``` + +## Real World Example + +```python demo +rx.flex( + rx.heading("Terms and Conditions"), + rx.text("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed neque elit, tristique placerat feugiat ac, facilisis vitae arcu. Proin eget egestas augue. Praesent ut sem nec arcu 'pellentesque aliquet. Duis dapibus diam vel metus tempus vulputate.", + ), + rx.checkbox("I certify that I have read and agree to the terms and conditions for this reservation.", spacing="2", size="2", default_checked=True), + rx.button("Book Reservation"), + direction="column", + align_items="start", + border="1px solid #e2e8f0", + background_color="#f7fafc", + border_radius="15px", + spacing="3", + padding="1em", +) +``` diff --git a/docs/library/forms/debounce.md b/docs/library/forms/debounce.md new file mode 100644 index 000000000..49bfb925b --- /dev/null +++ b/docs/library/forms/debounce.md @@ -0,0 +1,38 @@ +--- +components: + - rx.debounce_input +--- + +```python exec +import reflex as rx +``` + +# Debounce + +Reflex is a backend-centric framework, which can create negative performance impacts for apps that need to provide interactive feedback to the user in real time. For example, if a search bar sends a request to the backend on every keystroke, it may result in a laggy UI. This is because the backend is doing a lot of work to process each keystroke, and the frontend is waiting for the backend to respond before updating the UI. + +Using the `rx.debounce_input` component allows the frontend to remain responsive while receiving user input and sends the value to the backend after some delay, on blur, or when `Enter` is pressed. + +"Typically, this component is used to wrap a child `rx.chakra.input` or `rx.text_area`, however, most child components that accept the `value` prop and `on_change` event handler can be used with `rx.debounce_input`." + +This example only sends the final checkbox state to the backend after a 1 second delay. + +```python demo exec +class DebounceCheckboxState(rx.State): + checked: bool = False + +def debounce_checkbox_example(): + return rx.hstack( + rx.cond( + DebounceCheckboxState.checked, + rx.text("Checked", color="green"), + rx.text("Unchecked", color="red"), + ), + rx.debounce_input( + rx.chakra.checkbox( + on_change=DebounceCheckboxState.set_checked, + ), + debounce_timeout=1000, + ), + ) +``` diff --git a/docs/library/forms/editor.md b/docs/library/forms/editor.md new file mode 100644 index 000000000..ba8a6b142 --- /dev/null +++ b/docs/library/forms/editor.md @@ -0,0 +1,147 @@ +--- +components: + - rx.editor +--- + +# Editor + +An HTML editor component based on [Suneditor](http://suneditor.com/sample/index.html). + +```python demo exec +import reflex as rx + + +class EditorState(rx.State): + content: str = "

Editor content

" + + def handle_change(self, content: str): + """Handle the editor value change.""" + self.content = content + + +def editor_example(): + return rx.vstack( + rx.editor( + set_contents=EditorState.content, + on_change=EditorState.handle_change, + ), + rx.box( + rx.html(EditorState.content), + border="1px dashed #ccc", + border_radius="4px", + width="100%", + ), + ) +``` + +# EditorOptions + +The extended options and toolbar buttons can be customized by passing an instance +of `rx.EditorOptions` for the `set_options` prop. + +```python exec +from pcweb import styles +from pcweb.pages.docs.source import Source, generate_docs +from pcweb.templates.docpage import h2_comp +editor_options_source = Source(module=rx.EditorOptions) +``` + +```python eval +rx.fragment( + h2_comp(text="Fields"), + rx.box(rx.chakra.table( + rx.chakra.thead( + rx.chakra.tr( + rx.chakra.th("Field"), + rx.chakra.th("Description"), + ) + ), + rx.chakra.tbody( + *[ + rx.chakra.tr( + rx.chakra.td(rx.code(field["name"], font_weight=styles.BOLD_WEIGHT)), + rx.chakra.td(field["description"]), + ) + for field in editor_options_source.get_fields() + ], + ), + ), style={"overflow": "auto"}), +) +``` + +```python eval +rx.box(height="2em") +``` + +The `button_list` prop expects a list of lists, where each sublist contains the +names of buttons forming a group on the toolbar. The character "/" can be used +in place of a sublist to denote a line break in the toolbar. + +Some valid `button_list` options are enumerated in `rx.EditorButtonList`, seen below. + +```python +class EditorButtonList(list, enum.Enum): + + BASIC = [ + ["font", "fontSize"], + ["fontColor"], + ["horizontalRule"], + ["link", "image"], + ] + FORMATTING = [ + ["undo", "redo"], + ["bold", "underline", "italic", "strike", "subscript", "superscript"], + ["removeFormat"], + ["outdent", "indent"], + ["fullScreen", "showBlocks", "codeView"], + ["preview", "print"], + ] + COMPLEX = [ + ["undo", "redo"], + ["font", "fontSize", "formatBlock"], + ["bold", "underline", "italic", "strike", "subscript", "superscript"], + ["removeFormat"], + "/", + ["fontColor", "hiliteColor"], + ["outdent", "indent"], + ["align", "horizontalRule", "list", "table"], + ["link", "image", "video"], + ["fullScreen", "showBlocks", "codeView"], + ["preview", "print"], + ["save", "template"], + ] +``` + +A custom list of toolbar buttons may also be specified using these names as seen +in the following example. + +Since this example uses the same state as above, the two editors contents are +shared and can be modified by either one. + +```python demo +rx.editor( + set_contents=EditorState.content, + set_options=rx.EditorOptions( + button_list=[ + ["font", "fontSize", "formatBlock"], + ["fontColor", "hiliteColor"], + ["bold", "underline", "italic", "strike", "subscript", "superscript"], + ["removeFormat"], + "/", + ["outdent", "indent"], + ["align", "horizontalRule", "list", "table"], + ["link"], + ["fullScreen", "showBlocks", "codeView"], + ["preview", "print"], + ] + ), + on_change=EditorState.handle_change, +) +``` + +See the [Suneditor README.md](https://github.com/JiHong88/suneditor/blob/master/README.md) for more +details on buttons and options. + +```python eval +rx.box(height="5em") +``` diff --git a/docs/library/forms/form-ll.md b/docs/library/forms/form-ll.md new file mode 100644 index 000000000..73ba8e7e2 --- /dev/null +++ b/docs/library/forms/form-ll.md @@ -0,0 +1,583 @@ +--- +components: + - rx.radix.form.root + - rx.radix.form.field + - rx.radix.form.control + #- rx.radix.form.label + - rx.radix.form.message + - rx.radix.form.submit + +FormRoot: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + ), + **props, + ) + + +FormField: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", match="typeMismatch"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + **props, + ), + reset_on_submit=True, + ) + + +FormMessage: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", **props,), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=True, + ) + + +--- + +# Form + +```python exec +import reflex as rx +import reflex.components.radix.primitives as rdxp +``` + +Forms are used to collect information from your users. Forms group the inputs and submit them together. + +This implementation is based on the [Radix forms](https://www.radix-ui.com/primitives/docs/components/form). + +## Basic Example + +Here is an example of a form collecting an email address, with built-in validation on the email. If email entered is invalid, the form cannot be submitted. Note that the `form.submit` button is not automatically disabled. It is still clickable, but does not submit the form data. After successful submission, an alert window shows up and the form is cleared. There are a few `flex` containers used in the example to control the layout of the form components. + +```python demo +rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", match="typeMismatch"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=True, +) +``` + +In this example, the `text_field.input` has an attribute `type="email"` and the `form.message` has the attribute `match="typeMismatch"`. Those are required for the form to validate the input by its type. The prop `as_child="True"` is required when using other components to construct a Form component. This example has used `text_field.input` to construct the Form Control and `button` the Form Submit. + +## Form Anatomy + +```python eval +rx.code_block( + """form.root( + form.field( + form.label(...), + form.control(...), + form.message(...), + ), + form.submit(...), +)""", + language="python", +) +``` + +A Form Root (`form.root`) contains all the parts of a form. The Form Field (`form.field`), Form Submit (`form.submit`), etc should all be inside a Form Root. A Form Field can contain a Form Label (`form.label`), a Form Control (`form.control`), and a Form Message (`form.message`). A Form Label is a label element. A Form Control is where the user enters the input or makes selections. By default, the Form Control is a input. Using other form components to construct the Form Control is supported. To do that, set the prop `as_child=True` on the Form Control. + +```md alert info +The current version of Radix Forms does not support composing **Form Control** with other Radix form primitives such as **Checkbox**, **Select**, etc. +``` + +The Form Message is a validation message which is automatically wired (functionality and accessibility). When the Form Control determines the input is invalid, the Form Message is shown. The `match` prop is to enable [client side validation](#client-side-validation). To perform [server side validation](#server-side-validation), **both** the `force_match` prop of the Form Control and the `server_invalid` prop of the Form Field are set together. + +The Form Submit is by default a button that submits the form. To use another button component as a Form Submit, include that button as a child inside `form.submit` and set the prop `as_child=True`. + +The `on_submit` prop of the Form Root accepts an event handler. It is called with the submitted form data dictionary. To clear the form after submission, set the `reset_on_submit=True` prop. + +## Data Submission + +As previously mentioned, the various pieces of data in the form are submitted together as a dictionary. The form control or the input components must have the `name` attribute. This `name` is the key to get the value from the form data dictionary. If no validation is needed, the form type components such as Checkbox, Radio Groups, TextArea can be included directly under the Form Root instead of inside a Form Control. + +```python demo exec +import reflex as rx +import reflex.components.radix.primitives as rdxp + +class RadixFormSubmissionState(rx.State): + form_data: dict + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + @rx.var + def form_data_keys(self) -> list: + return list(self.form_data.keys()) + + @rx.var + def form_data_values(self) -> list: + return list(self.form_data.values()) + + +def radix_form_submission_example(): + return rx.flex( + rx.form.root( + rx.flex( + rx.flex( + rx.checkbox( + default_checked=True, + name="box1", + ), + rx.text("box1 checkbox"), + direction="row", + spacing="2", + align="center", + ), + rx.radio.root( + rx.flex( + rx.radio.item(value="1"), + "1", + direction="row", + align="center", + spacing="2", + ), + rx.flex( + rx.radio.item(value="2"), + "2", + direction="row", + align="center", + spacing="2", + ), + rx.flex( + rx.radio.item(value="3"), + "3", + direction="row", + align="center", + spacing="2", + ), + default_value="1", + name="box2", + ), + rx.input.input( + placeholder="box3 textfield input", + name="box3", + ), + rx.select.root( + rx.select.trigger( + placeholder="box4 select", + ), + rx.select.content( + rx.select.group( + rx.select.item( + "Orange", + value="orange" + ), + rx.select.item( + "Apple", + value="apple" + ), + ), + ), + name="box4", + ), + rx.flex( + rx.switch( + default_checked=True, + name="box5", + ), + "box5 switch", + spacing="2", + align="center", + direction="row", + ), + rx.flex( + rx.slider( + default_value=[40], + width="100%", + name="box6", + ), + "box6 slider", + direction="row", + spacing="2", + align="center", + ), + rx.text_area( + placeholder="Enter for box7 textarea", + name="box7", + ), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="4", + ), + on_submit=RadixFormSubmissionState.handle_submit, + ), + rx.divider(size="4"), + rx.text( + "Results", + weight="bold", + ), + rx.foreach(RadixFormSubmissionState.form_data_keys, + lambda key, idx: rx.text(key, " : ", RadixFormSubmissionState.form_data_values[idx]) + ), + direction="column", + spacing="4", + ) +``` + +## Validation + +### Client Side Validation + +Client side validation is achieved by examining the property of an interface of HTML elements called **ValidityState**. The `match` prop of the Form Message determines when the message should be displayed. The valid `match` prop values can be found in the **props** tab at the top of this page. For example, `"typeMismatch"` is set to `True` when an input element has a `type` attribute and the entered value is not valid for the `type`. If the input is specified as `type="url"`, it is expected to start with `http://` or `https://`. For the list of supported types, please refer to [HTML input element docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#type). The above references are all part of the HTML standards. For more details, please refer to [ValidityState docs](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState) and further more the reference links on that page. + +Below is an example of a form that collects a **number** from a `text_field.input`. The number is in the range of **[30, 100]** (both ends of the range are inclusive: **30** and **100** are valid). When a number smaller than **30** is entered, a message below the input field is printed: **Please enter a number >= 30**. This is because `min=30` is set on the `text_field.input` and `match="rangeUnderflow"` on the `form.message`. Similarly, when a number larger than **100** is entered, this message **Please enter a number <= 100** is displayed. Note the `max=100` attribute on the `text_field.input` and `match="rangeOverflow"` on the `form.message`. + +```python demo +rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Requires number in range [30, 100]"), + rx.form.control( + rx.input.input( + placeholder="Enter a number", + type="number", + max=100, + min=30 + ), + as_child=True, + ), + rx.form.message("Please enter a number <= 100", match="rangeOverflow"), + rx.form.message("Please enter a number >= 30", match="rangeUnderflow"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="some_number", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=True, +) +``` + +Here is an example where the input text is expected to be at least a certain length. Note that the attribute `min_length` is written as snake case. Behind the scene, Reflex automatically convert this to the camel case `minLength` used in the frontend. + +```python demo +rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Please choose a password of length >= 8 characters"), + rx.form.control( + rx.input.input( + placeholder="Enter your password", + type="password", + min_length=8 + ), + as_child=True, + ), + rx.form.message("Please enter a password length >= 8", match="tooShort"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="user_password", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=True, +) +``` + +If the input follows certain patterns, setting `pattern` on the input and `match="patternMismatch"` on the `form.message` could be useful. Below is an example of a form that requires input to be precisely 10 digits. More information is available at [ValidityState: patternMismatch property](https://developer.mozilla.org/en-US/docs/Web/API/ValidityState/patternMismatch). + +```python demo +rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Please enter your phone number with only digits. Let's say in your region the phone number is exactly 10 digits long."), + rx.form.control( + rx.input.input( + placeholder="Enter your your phone number", + type="text", + pattern="[0-9]{10}", + ), + as_child=True, + ), + rx.form.message( + "Please enter a valid phone number", + match="patternMismatch", + ), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="phone_number", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=True, +) +``` + +Below is an example of `"typeMismatch"` validation. + +```python demo +rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Please enter a valid URL starting with http or https"), + rx.form.control( + rx.input.input( + placeholder="Enter your URL", + type="url", + ), + as_child=True, + ), + rx.form.message("Please enter a valid URL", match="typeMismatch"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="user_url", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=True, +) +``` + +### Server Side Validation + +Server side validation is done through **Computed Vars** on the State. The **Var** should return a boolean flag indicating when input is invalid. Set that **Var** on both the `server_invalid` prop of `form.field` and the `force_match` prop of `form.message`. There is an example how to do that in the [Final Example](#final-example). + +## Final Example + +The final example shows a form that collects username and email during sign-up and validates them using server side validation. When server side validation fails, messages are displayed in red to show what is not accepted in the form, and the submit button is disabled. After submission, the collected form data is displayed in texts below the form and the form is cleared. + +```python demo exec +import re +import reflex as rx +import reflex.components.radix.primitives as rdxp + +class RadixFormState(rx.State): + # These track the user input real time for validation + user_entered_username: str + user_entered_email: str + + # These are the submitted data + username: str + email: str + + mock_username_db: list[str] = ["reflex", "admin"] + + @rx.var + def invalid_email(self) -> bool: + return not re.match(r"[^@]+@[^@]+\.[^@]+", self.user_entered_email) + + @rx.var + def username_empty(self) -> bool: + return not self.user_entered_username.strip() + + @rx.var + def username_is_taken(self) -> bool: + return self.user_entered_username in self.mock_username_db + + @rx.var + def input_invalid(self) -> bool: + return self.invalid_email or self.username_is_taken or self.username_empty + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.username = form_data.get("username") + self.email = form_data.get("email") + +def radix_form_example(): + return rx.flex( + rx.form.root( + rx.flex( + rx.form.field( + rx.flex( + rx.form.label("Username"), + rx.form.control( + rx.input.input( + placeholder="Username", + # workaround: `name` seems to be required when on_change is set + on_change=RadixFormState.set_user_entered_username, + name="username", + ), + as_child=True, + ), + # server side validation message can be displayed inside a rx.cond + rx.cond( + RadixFormState.username_empty, + rx.form.message( + "Username cannot be empty", + color="var(--red-11)", + ), + ), + # server side validation message can be displayed by `force_match` prop + rx.form.message( + "Username already taken", + # this is a workaround: + # `force_match` does not work without `match` + # This case does not want client side validation + # and intentionally not set `required` on the input + # so "valueMissing" is always false + match="valueMissing", + force_match=RadixFormState.username_is_taken, + color="var(--red-11)", + ), + direction="column", + spacing="2", + align="stretch", + ), + name="username", + server_invalid=RadixFormState.username_is_taken, + ), + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input.input( + placeholder="Email Address", + on_change=RadixFormState.set_user_entered_email, + name="email", + ), + as_child=True, + ), + rx.form.message( + "A valid Email is required", + match="valueMissing", + force_match=RadixFormState.invalid_email, + color="var(--red-11)", + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + server_invalid=RadixFormState.invalid_email, + ), + rx.form.submit( + rx.button( + "Submit", + disabled=RadixFormState.input_invalid, + ), + as_child=True, + ), + direction="column", + spacing="4", + width="25em", + ), + on_submit=RadixFormState.handle_submit, + reset_on_submit=True, + ), + rx.divider(size="4"), + rx.text( + "Username submitted: ", + rx.text( + RadixFormState.username, + weight="bold", + color="var(--accent-11)", + ), + ), + rx.text( + "Email submitted: ", + rx.text( + RadixFormState.email, + weight="bold", + color="var(--accent-11)", + ), + ), + direction="column", + spacing="4", + ) +``` diff --git a/docs/library/forms/form.md b/docs/library/forms/form.md new file mode 100644 index 000000000..101899aa6 --- /dev/null +++ b/docs/library/forms/form.md @@ -0,0 +1,228 @@ +--- +components: + - rx.radix.form + - rx.radix.form.root + - rx.radix.form.field + - rx.radix.form.control + - rx.radix.form.label + - rx.radix.form.message + - rx.radix.form.submit + +FormRoot: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + ), + **props, + ) + + +FormField: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", match="typeMismatch"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + **props, + ), + reset_on_submit=True, + ) + + +FormLabel: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email", **props,), + rx.form.control( + rx.input.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", match="typeMismatch"), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + ), + reset_on_submit=True, + ) + +FormMessage: | + lambda **props: rx.form.root( + rx.form.field( + rx.flex( + rx.form.label("Email"), + rx.form.control( + rx.input.input( + placeholder="Email Address", + # type attribute is required for "typeMismatch" validation + type="email", + ), + as_child=True, + ), + rx.form.message("Please enter a valid email", **props,), + rx.form.submit( + rx.button("Submit"), + as_child=True, + ), + direction="column", + spacing="2", + align="stretch", + ), + name="email", + ), + on_submit=lambda form_data: rx.window_alert(form_data.to_string()), + reset_on_submit=True, + ) + + +--- + +```python exec +import reflex as rx +``` + +# Form + +Forms are used to collect user input. The `rx.form` component is used to group inputs and submit them together. + +The form component's children can be form controls such as `rx.input`, `rx.checkbox`, `rx.slider`, `rx.textarea`, `rx.radio_group`, `rx.select` or `rx.switch`. The controls should have a `name` attribute that is used to identify the control in the form data. The `on_submit` event trigger submits the form data as a dictionary to the `handle_submit` event handler. + +The form is submitted when the user clicks the submit button or presses enter on the form controls. + +```python demo exec +class FormState(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_example(): + return rx.vstack( + rx.form( + rx.vstack( + rx.input(placeholder="First Name", name="first_name"), + rx.input(placeholder="Last Name", name="last_name"), + rx.hstack( + rx.checkbox("Checked", name="check"), + rx.switch("Switched", name="switch"), + ), + rx.button("Submit", type="submit"), + ), + on_submit=FormState.handle_submit, + reset_on_submit=True, + ), + rx.divider(), + rx.heading("Results"), + rx.text(FormState.form_data.to_string()), + ) +``` + +```md alert warning +# When using the form you must include a button or input with `type='submit'`. +``` + +## Dynamic Forms + +Forms can be dynamically created by iterating through state vars using `rx.foreach`. + +This example allows the user to add new fields to the form prior to submit, and all +fields will be included in the form data passed to the `handle_submit` function. + +```python demo exec +class DynamicFormState(rx.State): + form_data: dict = {} + form_fields: list[str] = ["first_name", "last_name", "email"] + + @rx.cached_var + def form_field_placeholders(self) -> list[str]: + return [ + " ".join(w.capitalize() for w in field.split("_")) + for field in self.form_fields + ] + + def add_field(self, form_data: dict): + new_field = form_data.get("new_field") + if not new_field: + return + field_name = new_field.strip().lower().replace(" ", "_") + self.form_fields.append(field_name) + + def handle_submit(self, form_data: dict): + self.form_data = form_data + + +def dynamic_form(): + return rx.vstack( + rx.form( + rx.vstack( + rx.foreach( + DynamicFormState.form_fields, + lambda field, idx: rx.input( + placeholder=DynamicFormState.form_field_placeholders[idx], + name=field, + ), + ), + rx.button("Submit", type="submit"), + ), + on_submit=DynamicFormState.handle_submit, + reset_on_submit=True, + ), + rx.form( + rx.hstack( + rx.input(placeholder="New Field", name="new_field"), + rx.button("+", type="submit"), + ), + on_submit=DynamicFormState.add_field, + reset_on_submit=True, + ), + rx.divider(), + rx.heading("Results"), + rx.text(DynamicFormState.form_data.to_string()), + ) +``` diff --git a/docs/library/forms/input-ll.md b/docs/library/forms/input-ll.md new file mode 100644 index 000000000..b62a8756d --- /dev/null +++ b/docs/library/forms/input-ll.md @@ -0,0 +1,123 @@ +--- +components: + - rx.radix.text_field + - rx.radix.text_field.root + - rx.radix.text_field.input + - rx.radix.text_field.slot +--- + +```python exec +import reflex as rx +``` + +# TextField + +A text field is an input field that users can type into. This component uses Radix's [text field](https://radix-ui.com/primitives/docs/components/text-field) component. + +## Basic Example + +```python demo +rx.radix.text_field.root( + rx.radix.text_field.slot( + rx.icon(tag="search"), + ), + rx.radix.text_field.input( + placeholder="Search here...", + ), +) +``` + +```python demo exec +class TextfieldBlur1(rx.State): + text: str = "Hello World!" + + +def blur_example1(): + return rx.vstack( + rx.heading(TextfieldBlur1.text), + rx.radix.text_field.root( + rx.radix.text_field.slot( + rx.icon(tag="search"), + ), + rx.radix.text_field.input( + placeholder="Search here...", + on_blur=TextfieldBlur1.set_text, + ), + + ) + ) +``` + +```python demo exec +class TextfieldControlled1(rx.State): + text: str = "Hello World!" + + +def controlled_example1(): + return rx.vstack( + rx.heading(TextfieldControlled1.text), + rx.radix.text_field.root( + rx.radix.text_field.slot( + rx.icon(tag="search"), + ), + rx.radix.text_field.input( + placeholder="Search here...", + value=TextfieldControlled1.text, + on_change=TextfieldControlled1.set_text, + ), + ), + ) +``` + +# Real World Example + +```python demo exec + +def song(title, initials: str, genre: str): + return rx.card(rx.flex( + rx.flex( + rx.avatar(fallback=initials), + rx.flex( + rx.text(title, size="2", weight="bold"), + rx.text(genre, size="1", color_scheme="gray"), + direction="column", + spacing="1", + ), + direction="row", + align_items="left", + spacing="1", + ), + rx.flex( + rx.icon(tag="chevron_right"), + align_items="center", + ), + justify="between", + )) + +def search(): + return rx.card( + rx.flex( + rx.radix.text_field.root( + rx.radix.text_field.slot( + rx.icon(tag="search"), + ), + rx.radix.text_field.input( + placeholder="Search songs...", + ), + ), + rx.flex( + song("The Less I Know", "T", "Rock"), + song("Breathe Deeper", "ZB", "Rock"), + song("Let It Happen", "TF", "Rock"), + song("Borderline", "ZB", "Pop"), + song("Lost In Yesterday", "TO", "Rock"), + song("Is It True", "TO", "Rock"), + direction="column", + spacing="1", + ), + direction="column", + spacing="3", + ), + style={"maxWidth": 500}, +) +``` diff --git a/docs/library/forms/input.md b/docs/library/forms/input.md new file mode 100644 index 000000000..92899d818 --- /dev/null +++ b/docs/library/forms/input.md @@ -0,0 +1,186 @@ +--- +components: + - rx.radix.input + - rx.radix.text_field.root + - rx.radix.text_field.input + - rx.radix.text_field.slot + + +Input: | + lambda **props: rx.input(placeholder="Search the docs", **props) + +TextFieldRoot: | + lambda **props: rx.radix.themes.text_field.root( + rx.radix.themes.text_field.slot( + rx.icon(tag="search", height="16", width="16"), + ), + rx.radix.themes.text_field.input(placeholder="Search the docs"), + **props, + ) + +TextFieldInput: | + lambda **props: rx.radix.themes.text_field.root( + rx.radix.themes.text_field.slot( + rx.icon(tag="search", height="16", width="16"), + ), + rx.radix.themes.text_field.input(placeholder="Search the docs", **props,), + ) + +TextFieldSlot: | + lambda **props: rx.radix.themes.text_field.root( + rx.radix.themes.text_field.slot( + rx.icon(tag="search", height="16", width="16"), + **props, + ), + rx.radix.themes.text_field.input(placeholder="Search the docs"), + ) +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` + +# Input (High Level API for TextField) + +The `input` component is an input field that users can type into. + +## Basic Example + +```python demo +rx.input() +``` + +### Setting Defaults + +Can set defaults for a `placeholder` for text to show in the `input` box before any text is input into it. + +Can limit the `max_length` allowed as input into the `input` box. + +```python demo +rx.input(placeholder="Search here...", max_length="20") +``` + +### Using Event Handlers + +The `on_blur` event handler is called when focus has left the `input` for example, it’s called when the user clicks outside of a focused text input. + +```python demo exec +class TextfieldBlur(rx.State): + text: str = "Hello World!" + + +def blur_example(): + return rx.vstack( + rx.heading(TextfieldBlur.text), + rx.input( + placeholder="Search here...", + on_blur=TextfieldBlur.set_text, + ), + ) +``` + +The `on_change` event handler is called when the `value` of `input` has changed. + +```python demo exec +class TextfieldControlled(rx.State): + text: str = "Hello World!" + + +def controlled_example(): + return rx.vstack( + rx.heading(TextfieldControlled.text), + rx.input( + placeholder="Search here...", + value=TextfieldControlled.text, + on_change=TextfieldControlled.set_text, + ), + ) +``` + +Behind the scene, the input component is implemented using debounced input to avoid sending individual state updates per character to the backend while the user is still typing. This allows a state var to directly control the `value` prop from the backend without the user experiencing input lag. For advanced use cases, you can tune the debounce delay by setting the `debounce_timeout` when creating the Input component. You can find examples of how it is used in the [DebouncedInput]({library.forms.debounce.path}) component. + +### Submitting a form using input + +The `name` prop is needed to submit with its owning form as part of a name/value pair. + +When the `required` prop is `True`, it indicates that the user must input text before the owning form can be submitted. + +The `type` is set here to `password`. The element is presented as a one-line plain text editor control in which the text is obscured so that it cannot be read. The `type` prop can take any value of `email`, `file`, `password`, `text` and several others. Learn more [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input). + +```python demo exec +class FormInputState(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_input1(): + return rx.vstack( + rx.form.root( + rx.vstack( + rx.input(name="input", default_value="search", placeholder="Input text here...", type="password", required=True), + rx.button("Submit", type="submit"), + width="100%", + ), + on_submit=FormInputState.handle_submit, + reset_on_submit=True, + width="100%", + ), + rx.divider(width="100%"), + rx.heading("Results"), + rx.text(FormInputState.form_data.to_string()), + width="100%", + ) +``` + +To learn more about how to use forms in the [Form]({library.forms.form.path}) docs. + +## Real World Example + +```python demo exec + +def song(title, initials: str, genre: str): + return rx.card(rx.flex( + rx.flex( + rx.avatar(fallback=initials), + rx.flex( + rx.text(title, size="2", weight="bold"), + rx.text(genre, size="1", color_scheme="gray"), + direction="column", + spacing="1", + ), + direction="row", + align_items="left", + spacing="1", + ), + rx.flex( + rx.icon(tag="chevron_right"), + align_items="center", + ), + justify="between", + )) + + +def search(): + return rx.card( + rx.flex( + rx.input(placeholder="Search songs...", ), + rx.flex( + song("The Less I Know", "T", "Rock"), + song("Breathe Deeper", "ZB", "Rock"), + song("Let It Happen", "TF", "Rock"), + song("Borderline", "ZB", "Pop"), + song("Lost In Yesterday", "TO", "Rock"), + song("Is It True", "TO", "Rock"), + direction="column", + spacing="1", + ), + direction="column", + spacing="3", + ), + style={"maxWidth": 500}, +) +``` diff --git a/docs/library/forms/radio_group-ll.md b/docs/library/forms/radio_group-ll.md new file mode 100644 index 000000000..0ef84d3c9 --- /dev/null +++ b/docs/library/forms/radio_group-ll.md @@ -0,0 +1,504 @@ +--- +components: + - rx.radix.radio_group + - rx.radix.radio_group.root + - rx.radix.radio_group.item +--- + + +```python exec +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +# Radio Group + +A set of interactive radio buttons where only one can be selected at a time. + +## Basic example + +The `rx.radio.root` contains all the parts of a radio group. The `rx.radio.item` is an item in the group that can be checked. + +```python demo +rx.radio.root( + rx.radio.item(value="1"), + rx.radio.item(value="2"), + rx.radio.item(value="3"), + default_value="1", +) + +``` + +The `default_value` prop is used to set the value of the radio item that should be checked when initially rendered. + +## Radio Group Root + +### Control the value + +The state can specify which item in a radio group is checked by setting the `value` prop, +making the radio group a fully-controlled input. To allow the user to change the selected +value by clicking, the `on_change` event handler must be defined to update +the Var representing the current `value`. + +```python demo exec +class RadioState1(rx.State): + val: str = "" + + @rx.cached_var + def display_value(self): + return self.val or "No Selection" + + +def radio_state_example(): + return rx.flex( + rx.badge( + RadioState1.display_value, + color_scheme="green" + ), + rx.radio.root( + rx.radio.item(value="1"), + rx.radio.item(value="2"), + rx.radio.item(value="3"), + value=RadioState1.val, + on_change=RadioState1.set_val, + ), + rx.button("Clear", on_click=RadioState1.set_val("")), + align="center", + justify="center", + direction="column", + spacing="2", + ) +``` + +When the `disabled` prop is set to `True`, it prevents the user from interacting with radio items. + +```python demo +rx.flex( + rx.radio.root( + rx.radio.item(value="1"), + rx.radio.item(value="2"), + ), + rx.radio.root( + rx.radio.item(value="1"), + rx.radio.item(value="2"), + disabled=True, + ), + spacing="2", +) + +``` + +### Submitting a form using Radio Group + +The `name` prop is used to name the group. It is submitted with its owning form as part of a name/value pair. + +When the `required` prop is `True`, it indicates that the user must check a radio item before the owning form can be submitted. + +```python demo exec +class FormRadioState(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_example(): + return rx.flex( + rx.form.root( + rx.flex( + rx.radio.root( + "Radio Group ", + rx.radio.item(value="1"), + rx.radio.item(value="2"), + rx.radio.item(value="3"), + name="radio", + required=True, + ), + rx.button("Submit", type="submit"), + direction="column", + spacing="2", + ), + on_submit=FormRadioState.handle_submit, + reset_on_submit=True, + ), + rx.divider(size="4"), + rx.heading("Results"), + rx.text(FormRadioState.form_data.to_string()), + direction="column", + spacing="2", + ) +``` + +## Radio Group Item + +### value + +The `value` given as data when submitted with a `name` on `rx.radio.root`. + +### disabled + +Use the `disabled` prop to create a disabled radiobutton. When `True`, prevents the user from interacting with the radio item. This differs from the `disabled` prop used by the `rx.radio.root`, which allows you to disable all the `rx.radio.item` components within the `rx.radio.root`. + +```python demo +rx.flex( + rx.radio.root( + rx.flex( + rx.text( + rx.flex( + rx.radio.item(value="1"), + "Off", + spacing="2", + ), + as_="label", + size="2", + ), + rx.text( + rx.flex( + rx.radio.item(value="2"), + "On", + spacing="2", + ), + as_="label", + size="2", + ), + direction="column", + spacing="2", + ), + ), + rx.radio.root( + rx.flex( + rx.text( + rx.flex( + rx.radio.item(value="1", disabled=True), + "Off", + spacing="2", + ), + as_="label", + size="2", + color="gray", + ), + rx.text( + rx.flex( + rx.radio.item(value="2"), + "On", + spacing="2", + ), + as_="label", + size="2", + color="gray", + ), + direction="column", + spacing="2", + ), + ), + direction="column", + spacing="2", + +) +``` + +### required + +When `True`, indicates that the user must check the `radio_item_group` before the owning form can be submitted. This can only be used when a single `rx.radio.item` is used. + +```python demo exec +class FormRadioState2(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_example2(): + return rx.flex( + rx.form.root( + rx.flex( + rx.radio.root( + rx.radio.item(value="1", required=True), + name="radio", + ), + rx.button("Submit", type="submit"), + direction="column", + spacing="2", + ), + on_submit=FormRadioState2.handle_submit, + reset_on_submit=True, + ), + rx.divider(size="4"), + rx.heading("Results"), + rx.text(FormRadioState2.form_data.to_string()), + direction="column", + spacing="2", + ) +``` + +## Styling + +### size + +```python demo +rx.flex( + rx.radio.root( + rx.radio.item(value="1"), + size="1", + ), + rx.radio.root( + rx.radio.item(value="1"), + size="2", + ), + rx.radio.root( + rx.radio.item(value="1"), + size="3", + ), + spacing="2", +) + +``` + +### variant + +```python demo +rx.flex( + rx.flex( + rx.radio.root( + rx.radio.item(value="1"), + rx.radio.item(value="2"), + variant="surface", + default_value="1", + ), + direction="column", + spacing="2", + as_child=True, + ), + rx.flex( + rx.radio.root( + rx.radio.item(value="1"), + rx.radio.item(value="2"), + variant="classic", + default_value="1", + ), + direction="column", + spacing="2", + as_child=True, + ), + rx.flex( + rx.radio.root( + rx.radio.item(value="1"), + rx.radio.item(value="2"), + variant="soft", + default_value="1", + ), + direction="column", + spacing="2", + as_child=True, + ), + spacing="2", +) +``` + +### color + +```python demo +rx.flex( + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="indigo", + default_value="1", + ), + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="cyan", + default_value="1", + ), + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="orange", + default_value="1", + ), + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="crimson", + default_value="1", + ), + spacing="2" +) +``` + +### high_contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.grid( + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="cyan", + default_value="1", + ), + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="cyan", + default_value="1", + high_contrast=True, + ), + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="indigo", + default_value="1", + ), + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="indigo", + default_value="1", + high_contrast=True, + ), + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="orange", + default_value="1", + ), + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="orange", + default_value="1", + high_contrast=True, + ), + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="crimson", + default_value="1", + ), + rx.radio.root( + rx.radio.item(value="1"), + color_scheme="crimson", + default_value="1", + high_contrast=True, + ), + rows="2", + spacing="2", + display="inline-grid", + flow="column" +) +``` + +### alignment + +Composing `rx.radio.item` within `text` automatically centers it with the first line of text. + +```python demo +rx.flex( + rx.radio.root( + rx.text( + rx.flex( + rx.radio.item(value="1"), + "Default", + spacing="2", + ), + size="2", + as_="label", + ), + rx.text( + rx.flex( + rx.radio.item(value="2"), + "Compact", + spacing="2", + ), + size="2", + as_="label", + ), + default_value="1", + size="1", + ), + rx.radio.root( + rx.text( + rx.flex( + rx.radio.item(value="1"), + "Default", + spacing="2", + ), + size="3", + as_="label", + ), + rx.text( + rx.flex( + rx.radio.item(value="2"), + "Compact", + spacing="2", + ), + size="3", + as_="label", + ), + default_value="1", + size="2", + ), + rx.radio.root( + rx.text( + rx.flex( + rx.radio.item(value="1"), + "Default", + spacing="2", + ), + size="4", + as_="label", + ), + rx.text( + rx.flex( + rx.radio.item(value="2"), + "Compact", + spacing="2", + ), + size="4", + as_="label", + ), + default_value="1", + size="3", + ), + spacing="3", + direction="column", +) +``` + +```python eval +style_grid(component_used=rx.radio.root, component_used_str="radiogrouproot", variants=["classic", "surface", "soft"], components_passed=rx.radio.item(), disabled=True,) +``` + +## Real World Example + +```python demo +rx.radio.root( + rx.flex( + rx.text( + rx.flex( + rx.radio.item(value="1"), + "Default", + spacing="2", + ), + size="2", + as_="label", + ), + rx.text( + rx.flex( + rx.radio.item(value="2"), + "Comfortable", + gap="2", + ), + size="2", + as_="label", + ), + rx.text( + rx.flex( + rx.radio.item(value="3"), + "Compact", + gap="2", + ), + size="2", + as_="label", + ), + direction="column", + gap="2", + ), + default_value="1", +) +``` diff --git a/docs/library/forms/radio_group.md b/docs/library/forms/radio_group.md new file mode 100644 index 000000000..07ca860de --- /dev/null +++ b/docs/library/forms/radio_group.md @@ -0,0 +1,128 @@ +--- +components: + - rx.radix.radio_group + - rx.radix.radio_group.root + - rx.radix.radio_group.item + +HighLevelRadioGroup: | + lambda **props: rx.radix.themes.radio_group(["1", "2", "3", "4", "5"], **props) + +RadioGroupRoot: | + lambda **props: rx.radix.themes.radio_group.root( + rx.radix.themes.radio_group.item(value="1"), + rx.radix.themes.radio_group.item(value="2"), + rx.radix.themes.radio_group.item(value="3"), + rx.radix.themes.radio_group.item(value="4"), + rx.radix.themes.radio_group.item(value="5"), + **props + ) + +RadioGroupItem: | + lambda **props: rx.radix.themes.radio_group.root( + rx.radix.themes.radio_group.item(value="1", **props), + rx.radix.themes.radio_group.item(value="2", **props), + rx.radix.themes.radio_group.item(value="3",), + rx.radix.themes.radio_group.item(value="4",), + rx.radix.themes.radio_group.item(value="5",), + ) +--- + + +```python exec +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +# High Level Radio Group + +A set of interactive radio buttons where only one can be selected at a time. + +## Basic example + +```python demo +rx.radio(["1", "2", "3"], default_value="1") +``` + +The `default_value` prop can be used to set the value of the radio item that should be checked when initially rendered. + +## Setting direction, spacing and size + +The direction of the `radio_group` can be set using the `direction` prop which takes values `'row' | 'column' | 'row-reverse' | 'column-reverse' |`. + +The gap between the `radio_group` items can also be set using the `gap` prop, which takes values `'1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |`. + +The size of the `radio_group` items and the associated text can be set with the `size` prop, which can take values `1' | '2' | '3' |` + +```python demo +rx.radio(["1", "2", "3", "4", "5"], direction="row", spacing="8", size="3") +``` + +## Using State Vars in the RadioGroup + +State vars can also be passed in as the `items` to the `radiogroup`. + +```python demo exec +class RadioState_HL1(rx.State): + items: list[str] = ["1", "2", "3"] + +def radio_state_example_HL1(): + return rx.radio(RadioState_HL1.items, direction="row", spacing="9") +``` + +### Control the value + +The controlled `value` of the radio item to check. Should be used in conjunction with `on_change` event handler. + +```python demo exec +class RadioState_HL(rx.State): + text: str = "No Selection" + + +def radio_state_example_HL(): + return rx.vstack( + rx.badge(RadioState_HL.text, color_scheme="green"), + rx.radio(["1", "2", "3"], on_change=RadioState_HL.set_text), + ) +``` + +When the `disabled` prop is set to `True`, it prevents the user from interacting with radio items. + +```python demo +rx.flex( + rx.radio(["1", "2"]), + rx.radio(["1", "2"], disabled=True), + spacing="2", +) + +``` + +### Submitting a form using Radio Group + +The `name` prop is used to name the group. It is submitted with its owning form as part of a name/value pair. + +When the `required` prop is `True`, it indicates that the user must check a radio item before the owning form can be submitted. + +```python demo exec +class FormRadioState_HL(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_example_HL(): + return rx.vstack( + rx.form.root( + rx.vstack( + rx.radio(["1", "2", "3"], name="radio", required=True,), + rx.button("Submit", type="submit"), + ), + on_submit=FormRadioState_HL.handle_submit, + reset_on_submit=True, + ), + rx.divider(width="100%"), + rx.heading("Results"), + rx.text(FormRadioState_HL.form_data.to_string()), + ) +``` diff --git a/docs/library/forms/select-ll.md b/docs/library/forms/select-ll.md new file mode 100644 index 000000000..ea65aa235 --- /dev/null +++ b/docs/library/forms/select-ll.md @@ -0,0 +1,296 @@ +--- +components: + - rx.radix.select + - rx.radix.select.root + - rx.radix.select.trigger + - rx.radix.select.content + - rx.radix.select.group + - rx.radix.select.item + - rx.radix.select.label + - rx.radix.select.separator +--- + + +```python exec +import random +import reflex as rx +import reflex.components.radix.primitives as rdxp +from pcweb.templates.docpage import style_grid +``` + +# Select + +Displays a list of options for the user to pick from, triggered by a button. + +## Basic Example + +```python demo +rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.label("Fruits"), + rx.select.item("Orange", value="orange"), + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape", disabled=True), + ), + rx.select.separator(), + rx.select.group( + rx.select.label("Vegetables"), + rx.select.item("Carrot", value="carrot"), + rx.select.item("Potato", value="potato"), + ), + ), + default_value="apple", +) +``` + +## Usage + +## Disabling + +It is possible to disable individual items in a `select` using the `disabled` prop associated with the `rx.select.item`. + +```python demo +rx.select.root( + rx.select.trigger(placeholder="Select a Fruit"), + rx.select.content( + rx.select.group( + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape", disabled=True), + rx.select.item("Pineapple", value="pineapple"), + ), + ), +) +``` + +To prevent the user from interacting with select entirely, set the `disabled` prop to `True` on the `rx.select.root` component. + +```python demo +rx.select.root( + rx.select.trigger(placeholder="This is Disabled"), + rx.select.content( + rx.select.group( + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape"), + ), + ), + disabled=True, +) +``` + +## Setting Defaults + +It is possible to set several default values when constructing a `select`. + +The `placeholder` prop in the `rx.select.trigger` specifies the content that will be rendered when `value` or `default_value` is empty or not set. + +```python demo +rx.select.root( + rx.select.trigger(placeholder="pick a fruit"), + rx.select.content( + rx.select.group( + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape"), + ), + ), +) +``` + +The `default_value` in the `rx.select.root` specifies the value of the `select` when initially rendered. +The `default_value` should correspond to the `value` of a child `rx.select.item`. + +```python demo +rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape"), + ), + ), + default_value="apple", +) +``` + +## Fully controlled + +The `on_change` event trigger is fired when the value of the select changes. +In this example the `rx.select_root` `value` prop specifies which item is selected, and this +can also be controlled using state and a button without direct interaction with the select component. + +```python demo exec +class SelectState2(rx.State): + + values: list[str] = ["apple", "grape", "pear"] + + value: str = "" + + def choose_randomly(self): + """Change the select value var.""" + original_value = self.value + while self.value == original_value: + self.value = random.choice(self.values) + + +def select_example2(): + return rx.vstack( + rx.select.root( + rx.select.trigger(placeholder="No Selection"), + rx.select.content( + rx.select.group( + rx.foreach(SelectState2.values, lambda x: rx.select.item(x, value=x)) + ), + ), + value=SelectState2.value, + on_change=SelectState2.set_value, + + ), + rx.button("Choose Randomly", on_click=SelectState2.choose_randomly), + rx.button("Reset", on_click=SelectState2.set_value("")), + ) +``` + +The `open` prop and `on_open_change` event trigger work similarly to `value` and `on_change` to control the open state of the select. +If `on_open_change` handler does not alter the `open` prop, the select will not be able to be opened or closed by clicking on the +`select_trigger`. + + ```python demo exec +class SelectState8(rx.State): + is_open: bool = False + +def select_example8(): + return rx.flex( + rx.select.root( + rx.select.trigger(placeholder="No Selection"), + rx.select.content( + rx.select.group( + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape"), + ), + ), + open=SelectState8.is_open, + on_open_change=SelectState8.set_is_open, + ), + rx.button("Toggle", on_click=SelectState8.set_is_open(~SelectState8.is_open)), + spacing="2", + ) +``` + +### Submitting a Form with Select + +When a select is part of a form, the `name` prop of the `rx.select.root` sets the key that will be submitted with the form data. + +The `value` prop of `rx.select.item` provides the value to be associated with the `name` key when the form is submitted with that item selected. + +When the `required` prop of the `rx.select.root` is `True`, it indicates that the user must select a value before the form may be submitted. + +```python demo exec +class FormSelectState(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_select(): + return rx.flex( + rx.form.root( + rx.flex( + rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.label("Fruits"), + rx.select.item("Orange", value="orange"), + rx.select.item("Apple", value="apple"), + rx.select.item("Grape", value="grape"), + ), + rx.select.separator(), + rx.select.group( + rx.select.label("Vegetables"), + rx.select.item("Carrot", value="carrot"), + rx.select.item("Potato", value="potato"), + ), + ), + default_value="apple", + name="select", + ), + rx.button("Submit"), + width="100%", + direction="column", + spacing="2", + ), + on_submit=FormSelectState.handle_submit, + reset_on_submit=True, + ), + rx.divider(size="4"), + rx.heading("Results"), + rx.text(FormSelectState.form_data.to_string()), + width="100%", + direction="column", + spacing="2", + ) +``` + +## Real World Example + +```python demo +rx.card( + rx.flex( + rx.image(src="/reflex_banner.png", width="100%", height="auto"), + rx.flex( + rx.heading("Reflex Swag", size="4", margin_bottom="4px"), + rx.heading("$99", size="6", margin_bottom="4px"), + direction="row", justify="between", + width="100%", + ), + rx.text("Reflex swag with a sense of nostalgia, as if they carry whispered tales of past adventures", size="2", margin_bottom="4px"), + rx.divider(size="4"), + rx.flex( + rx.flex( + rx.text("Color", size="2", margin_bottom="4px", color_scheme="gray"), + rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.item("Light", value="light"), + rx.select.item("Dark", value="dark"), + ), + ), + default_value="light", + ), + direction="column", + ), + rx.flex( + rx.text("Size", size="2", margin_bottom="4px", color_scheme="gray"), + rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.select.item("24", value="24"), + rx.select.item("26", value="26"), + rx.select.item("28", value="28", disabled=True), + rx.select.item("30", value="30"), + rx.select.item("32", value="32"), + rx.select.item("34", value="34"), + rx.select.item("36", value="36"), + ), + ), + default_value="30", + ), + direction="column", + ), + rx.button(rx.icon(tag="plus"), "Add"), + align="end", + justify="between", + spacing="2", + width="100%", + ), + width="15em", + direction="column", + spacing="2", + ), +) +``` diff --git a/docs/library/forms/select.md b/docs/library/forms/select.md new file mode 100644 index 000000000..81c4af3c0 --- /dev/null +++ b/docs/library/forms/select.md @@ -0,0 +1,234 @@ +--- +components: + - rx.radix.select + - rx.radix.select.root + - rx.radix.select.trigger + - rx.radix.select.content + - rx.radix.select.group + - rx.radix.select.item + - rx.radix.select.label + - rx.radix.select.separator + +HighLevelSelect: | + lambda **props: rx.radix.themes.select(["apple", "grape", "pear"], default_value="pear", **props) + +SelectRoot: | + lambda **props: rx.radix.themes.select.root( + rx.radix.themes.select.trigger(), + rx.radix.themes.select.content( + rx.radix.themes.select.group( + rx.radix.themes.select.item("apple", value="apple"), + rx.radix.themes.select.item("grape", value="grape"), + rx.radix.themes.select.item("pear", value="pear"), + ), + ), + default_value="pear", + **props + ) + +SelectTrigger: | + lambda **props: rx.radix.themes.select.root( + rx.radix.themes.select.trigger(**props), + rx.radix.themes.select.content( + rx.radix.themes.select.group( + rx.radix.themes.select.item("apple", value="apple"), + rx.radix.themes.select.item("grape", value="grape"), + rx.radix.themes.select.item("pear", value="pear"), + ), + ), + default_value="pear", + ) + +SelectContent: | + lambda **props: rx.radix.themes.select.root( + rx.radix.themes.select.trigger(), + rx.radix.themes.select.content( + rx.radix.themes.select.group( + rx.radix.themes.select.item("apple", value="apple"), + rx.radix.themes.select.item("grape", value="grape"), + rx.radix.themes.select.item("pear", value="pear"), + ), + **props, + ), + default_value="pear", + ) + +SelectItem: | + lambda **props: rx.radix.themes.select.root( + rx.radix.themes.select.trigger(), + rx.radix.themes.select.content( + rx.radix.themes.select.group( + rx.radix.themes.select.item("apple", value="apple", **props), + rx.radix.themes.select.item("grape", value="grape"), + rx.radix.themes.select.item("pear", value="pear"), + ), + ), + default_value="pear", + ) +--- + + +```python exec +import random +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +# High Level Select + +Displays a list of options for the user to pick from—triggered by a button. + +## Basic Example + +```python demo +rx.select(["Apple", "Orange", "Banana", "Grape", "Pear"]) +``` + +## Disabling + +To prevent the user from interacting with select, set the `disabled` prop to `True`. + +```python demo +rx.select(["Apple", "Orange", "Banana", "Grape", "Pear"], disabled=True) +``` + +## Setting Defaults + +It is possible to set several default values when constructing a `select`. + +Can set the `placeholder` prop, which is the content that will be rendered when no value or no default_value is set. + +Can set the `label` prop, which is a label in the `select`. + +```python demo +rx.select(["Apple", "Orange", "Banana", "Grape", "Pear"], placeholder="Selection of Fruits", label="Fruits") +``` + +Can set the `default_value` prop, which is the value of the `select` when initially rendered. + +```python demo +rx.select(["Apple", "Orange", "Banana", "Grape", "Pear"], default_value="Orange") +``` + +## Simple Styling + +Can set the `color`, `variant` and `radius` to easily style the `select`. + +```python demo +rx.select(["Apple", "Orange", "Banana", "Grape", "Pear"], color="pink", variant="soft", radius="full", width="100%") +``` + +## High control of select component (value and open changes) + +The `on_change` event is called when the value of the `select` changes. In this example we set the `value` prop to change the select `value` using a button in this case. + +```python demo exec +class SelectState3(rx.State): + + values: list[str] = ["apple", "grape", "pear"] + + value: str = "apple" + + def change_value(self): + """Change the select value var.""" + self.value = random.choice(self.values) + + +def select_example3(): + return rx.vstack( + rx.select( + SelectState3.values, + value=SelectState3.value, + on_change=SelectState3.set_value, + ), + rx.button("Change Value", on_click=SelectState3.change_value), + + ) +``` + +The `on_open_change` event handler acts in a similar way to the `on_change` and is called when the open state of the select changes. + +```python demo +rx.select( + ["apple", "grape", "pear"], + on_change=rx.window_alert("on_change event handler called"), +) + +``` + +### Submitting a form using select + +The `name` prop is needed to submit with its owning form as part of a name/value pair. + +When the `required` prop is `True`, it indicates that the user must select a value before the owning form can be submitted. + +```python demo exec +class FormSelectState1(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_select1(): + return rx.vstack( + rx.form.root( + rx.vstack( + rx.select( + ["apple", "grape", "pear"], + default_value="apple", + name="select", + ), + rx.button("Submit", type="submit"), + width="100%", + ), + on_submit=FormSelectState1.handle_submit, + reset_on_submit=True, + width="100%", + ), + rx.divider(width="100%"), + rx.heading("Results"), + rx.text(FormSelectState1.form_data.to_string()), + width="100%", + ) +``` + +## Real World Example + +```python demo +rx.card( + rx.vstack( + rx.image(src="/reflex_banner.png", width="100%", height="auto"), + rx.flex( + rx.heading("Reflex Swag", size="4", mb="1"), + rx.heading("$99", size="6", mb="1"), + direction="row", justify="between", + width="100%", + ), + rx.text("Reflex swag with a sense of nostalgia, as if they carry whispered tales of past adventures", size="2", mb="1"), + rx.divider(width="100%"), + rx.flex( + rx.flex( + rx.text("Color", size="2", mb="1", color_scheme="gray"), + rx.select(["light", "dark"], default_value="light"), + direction="column", + ), + rx.flex( + rx.text("Size", size="2", mb="1", color_scheme="gray"), + rx.select(["24", "26", "28", "30", "32", "34", "36"], default_value="30"), + direction="column", + ), + rx.flex( + rx.text(".", size="2",), + rx.button("Add to cart"), + direction="column", + ), + direction="row", + justify="between", + width="100%", + ), + width="20vw", + ), +) +``` diff --git a/docs/library/forms/slider.md b/docs/library/forms/slider.md new file mode 100644 index 000000000..64228403d --- /dev/null +++ b/docs/library/forms/slider.md @@ -0,0 +1,178 @@ +--- +components: + - rx.radix.slider + +Slider: | + lambda **props: rx.radix.themes.slider(default_value=40, height="50%", **props) + +--- + + +```python exec +import reflex as rx +from pcweb.templates.docpage import style_grid +``` + +# Slider + +Provides user selection from a range of values. + +## Basic Example + +```python demo +rx.slider(default_value=40) +``` + +### Setting slider defaults + +We can set the `min` and `max` values for the range of the slider. The defaults for `min` and `max` are 0 and 100. + +The stepping interval can also be adjusted by using the `step` prop. It defaults to 1. + +The `on_value_commit` event is called when the value changes at the end of an interaction. Useful when you only need to capture a final value e.g. to update a backend service. + +```python demo exec +class SliderVariationState(rx.State): + value: int = 50 + + def set_end(self, value: int): + self.value = value + +def slider_max_min_step(): + return rx.vstack( + rx.heading(SliderVariationState.value), + rx.text("Min=20 Max=240"), + rx.slider(default_value=40, min=20, max=240, on_value_commit=SliderVariationState.set_end), + rx.text("Step=5"), + rx.slider(default_value=40, step=5, on_value_commit=SliderVariationState.set_end), + rx.text("Step=0.5"), + rx.slider(default_value=40, step=0.5, on_value_commit=SliderVariationState.set_end), + width="100%", + ) +``` + +### Disabling + +When the `disabled` prop is set to `True`, it prevents the user from interacting with the slider. + +```python demo +rx.slider(default_value=40, disabled=True) +``` + +### Control the value + +The `default_value` is the value of the slider when initially rendered. It can be a `float` or if multiple thumbs to drag are required then it can be passed as a `List[float]`. Providing multiple values creates a range slider. + +```python demo +rx.slider(default_value=45.5) +``` + +```python demo +rx.slider(default_value=[40, 60]) +``` + +The `on_change` event is called when the `value` of the slider changes. + +```python demo exec +class SliderVariationState2(rx.State): + value: int = 50 + + def set_end(self, value: int): + self.value = value + + +def slider_on_change(): + return rx.vstack( + rx.heading(SliderVariationState2.value), + rx.slider(default_value=40, on_change=SliderVariationState2.set_end), + width="100%", + ) +``` + +### Submitting a form using slider + +The `name` of the slider. Submitted with its owning form as part of a name/value pair. + +```python demo exec +class FormSliderState(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_example2(): + return rx.vstack( + rx.form.root( + rx.vstack( + rx.slider(default_value=40, name="slider"), + rx.button("Submit", type="submit"), + width="100%", + ), + on_submit=FormSliderState.handle_submit, + reset_on_submit=True, + width="100%", + ), + rx.chakra.divider(), + rx.heading("Results"), + rx.text(FormSliderState.form_data.to_string()), + width="100%", + ) +``` + +### Orientation + +Use the `orientation` prop to change the orientation of the slider. + +```python demo +rx.slider(default_value=40, orientation="horizontal") +``` + +```python demo +rx.slider(default_value=40, height="4em", orientation="vertical") +``` + +## Styling + +```python eval +style_grid(component_used=rx.slider, component_used_str="slider", variants=["classic", "surface", "soft"], disabled=True, default_value=40) +``` + +### size + +```python demo +rx.flex( + rx.slider(default_value=25, size="1"), + rx.slider(default_value=25, size="2"), + rx.slider(default_value=25, size="3"), + direction="column", + spacing="4", + width="100%", +) +``` + +### high contrast + +```python demo +rx.flex( + rx.slider(default_value=25), + rx.slider(default_value=25, high_contrast=True), + direction="column", + spacing="4", + width="100%", +) +``` + +### radius + +```python demo +rx.flex( + rx.slider(default_value=25, radius="none"), + rx.slider(default_value=25, radius="small"), + rx.slider(default_value=25, radius="full"), + direction="column", + spacing="4", + width="100%", +) +``` diff --git a/docs/library/forms/switch.md b/docs/library/forms/switch.md new file mode 100644 index 000000000..02f194e32 --- /dev/null +++ b/docs/library/forms/switch.md @@ -0,0 +1,208 @@ +--- +components: + - rx.radix.switch + +Switch: | + lambda **props: rx.radix.themes.switch(**props) + +--- + +```python exec +import reflex as rx +from pcweb.templates.docpage import style_grid +from pcweb.pages.docs import vars +``` + +# Switch + +A toggle switch alternative to the checkbox. + +## Basic Example + +```python demo +rx.text( + rx.flex( + rx.switch(default_checked=True), + "Sync Settings", + spacing="2", + ) +) + +``` + +Here we set the `default_checked` prop to be `True` which sets the state of the switch when it is initially rendered. + +## Usage + +### Submitting a form using switch + +The `name` of the switch is needed to submit with its owning form as part of a name/value pair. + +When the `required` prop is `True`, it indicates that the user must check the switch before the owning form can be submitted. + +The `value` prop is only used for form submission, use the `checked` prop to control state of the `switch`. + +```python demo exec +class FormSwitchState(rx.State): + form_data: dict = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + +def form_switch(): + return rx.vstack( + rx.form.root( + rx.vstack( + rx.switch(name="s1"), + rx.switch(name="s2"), + rx.switch(name="s3", required=True), + rx.button("Submit", type="submit"), + width="100%", + ), + on_submit=FormSwitchState.handle_submit, + reset_on_submit=True, + width="100%", + ), + rx.chakra.divider(), + rx.heading("Results"), + rx.text(FormSwitchState.form_data.to_string()), + width="100%", + ) +``` + +### Control the value + +The `checked` prop is used to control the state of the switch. + +The event `on_change` is called when the state of the switch changes, when the `change_checked` event handler is called. + +The `disabled` prop when `True`, prevents the user from interacting with the switch. + +In our example below, even though the third switch is `disabled` we are still able to change whether it is checked or not using the `checked` prop. + +```python demo exec +class SwitchState2(rx.State): + + checked = True + + def change_checked(self, checked: bool): + """Change the switch checked var.""" + self.checked = checked + + +def switch_example2(): + return rx.hstack( + rx.switch( + checked=SwitchState2.checked, + on_change=SwitchState2.change_checked, + ), + rx.switch( + checked=~SwitchState2.checked, + on_change=lambda v: SwitchState2.change_checked(~v), + ), + rx.switch( + checked=SwitchState2.checked, + on_change=SwitchState2.change_checked, + disabled=True, + ), + ) +``` + +In this example we use the `~` operator, which is used to invert a var. To learn more check out [var operators]({vars.var_operations.path}). + +## Styling + +```python eval +style_grid(component_used=rx.switch, component_used_str="switch", variants=["classic", "surface", "soft"], disabled=True, default_checked=True) +``` + +## Real World Example + +```python demo exec +class FormSwitchState2(rx.State): + form_data: dict = {} + + cookie_types: dict[str, bool] = {} + + def handle_submit(self, form_data: dict): + """Handle the form submit.""" + self.form_data = form_data + + def update_cookies(self, cookie_type: str, enabled: bool): + self.cookie_types[cookie_type] = enabled + + +def form_switch2(): + return rx.vstack( + rx.dialog.root( + rx.dialog.trigger( + rx.button("View Cookie Settings", size="4", variant="outline") + ), + rx.dialog.content( + rx.form.root( + rx.dialog.title("Your Cookie Preferences"), + rx.dialog.description( + "Change your cookie preferences.", + size="2", + margin_bottom="16px", + ), + rx.flex( + rx.text( + rx.flex( + "Required", + rx.switch(default_checked=True, disabled=True, name="required"), + spacing="2", + justify="between", + ), + as_="div", size="2", margin_bottom="4px", weight="bold", + ), + + *[rx.flex( + rx.text(cookie_type.capitalize(), as_="div", size="2", margin_bottom="4px", weight="bold"), + rx.text( + rx.flex( + rx.cond( + FormSwitchState2.cookie_types[cookie_type], + "Enabled", + "Disabled", + ), + rx.switch( + name=cookie_type, + checked=FormSwitchState2.cookie_types[cookie_type], + on_change=lambda checked: FormSwitchState2.update_cookies(cookie_type, checked)), + spacing="2", + ), + as_="div", size="2", margin_bottom="4px", weight="bold", + ), + direction="row", justify="between", + ) + for cookie_type in ["functional", "performance", "analytics", "advertisement", "others"]], + + + + direction="column", + spacing="3", + ), + rx.flex( + rx.button("Save & Accept", type="submit"), + rx.dialog.close( + rx.button("Exit"), + ), + spacing="3", + margin_top="16px", + justify="end", + ), + on_submit=FormSwitchState2.handle_submit, + reset_on_submit=True, + width="100%", + ), + ), + ), + rx.chakra.divider(), + rx.heading("Results"), + rx.text(FormSwitchState2.form_data.to_string()), + width="100%", + ) +``` diff --git a/docs/library/forms/textarea.md b/docs/library/forms/textarea.md new file mode 100644 index 000000000..926c4e07c --- /dev/null +++ b/docs/library/forms/textarea.md @@ -0,0 +1,80 @@ +--- +components: + - rx.radix.text_area + +TextArea: | + lambda **props: rx.radix.themes.text_area(**props) +--- + +```python exec +import reflex as rx +``` + +# TextArea + +A text area is a multi-line text input field. This component uses Radix's [text area](https://radix-ui.com/primitives/docs/components/text-area) component. + +## Basic Example + +```python demo +rx.text_area( + placeholder="Type here...", +) +``` + +```python demo exec +class TextAreaBlur(rx.State): + text: str = "Hello World!" + + +def blur_example(): + return rx.vstack( + rx.heading(TextAreaBlur.text), + rx.text_area( + on_blur=TextAreaBlur.set_text, + ), + ) +``` + +```python demo exec +class TextAreaControlled(rx.State): + text: str = "Hello World!" + + +def controlled_example(): + return rx.vstack( + rx.heading(TextAreaControlled.text), + rx.text_area( + value=TextAreaControlled.text, + on_change=TextAreaControlled.set_text, + ), + rx.text_area( + value="Simon says: " + TextAreaControlled.text, + ), + ) +``` + +# Real World Example + +```python demo +rx.card( + rx.flex( + rx.text("Are you enjoying Reflex?"), + rx.text_area(placeholder="Write your feedback…"), + rx.flex( + rx.text("Attach screenshot?", size="2"), + rx.switch(size="1", default_checked=True), + justify="between", + ), + rx.grid( + rx.button("Back", variant="surface"), + rx.button("Send"), + columns="2", + spacing="2", + ), + direction="column", + spacing="3", + ), + style={"maxWidth": 500}, +) +``` diff --git a/docs/library/forms/upload.md b/docs/library/forms/upload.md new file mode 100644 index 000000000..8b2e7224f --- /dev/null +++ b/docs/library/forms/upload.md @@ -0,0 +1,254 @@ +--- +components: + - rx.upload +--- + +```python exec +import reflex as rx +``` + +# Upload + +The Upload component can be used to upload files to the server. + +You can pass components as children to customize its appearance. +You can upload files by clicking on the component or by dragging and dropping files onto it. + +```python demo +rx.upload( + rx.text("Drag and drop files here or click to select files"), + id="my_upload", + border="1px dotted rgb(107,99,246)", + padding="5em", +) +``` + +Selecting a file will add it to the browser's file list, which can be rendered on the frontend using the `rx.selected_files(id)` special Var. +To clear the selected files, you can use another special Var `rx.clear_selected_files(id)` as an event handler. +To upload the file, you need to bind an event handler and pass the file list. + +A full example is shown below. + +```python demo box +rx.image(src="/upload.gif") +``` + +```python +class State(rx.State): + \"""The app state.\""" + + # The images to show. + img: list[str] + + async def handle_upload(self, files: list[rx.UploadFile]): + \"""Handle the upload of file(s). + + Args: + files: The uploaded files. + \""" + for file in files: + upload_data = await file.read() + outfile = rx.get_upload_dir() / file.filename + + # Save the file. + with outfile.open("wb") as file_object: + file_object.write(upload_data) + + # Update the img var. + self.img.append(file.filename) + + +color = "rgb(107,99,246)" + + +def index(): + \"""The main view.\""" + return rx.vstack( + rx.upload( + rx.vstack( + rx.button("Select File", color=color, bg="white", border=f"1px solid \{color}"), + rx.text("Drag and drop files here or click to select files"), + ), + id="upload1", + border=f"1px dotted \{color}", + padding="5em", + ), + rx.hstack(rx.foreach(rx.selected_files("upload1"), rx.text)), + rx.button( + "Upload", + on_click=State.handle_upload(rx.upload_files(upload_id="upload1")), + ), + rx.button( + "Clear", + on_click=rx.clear_selected_files("upload1"), + ), + rx.foreach(State.img, lambda img: rx.image(src=rx.get_upload_url(img))), + padding="5em", + ) +``` + +In the example below, the upload component accepts a maximum number of 5 files of specific types. +It also disables the use of the space or enter key in uploading files. + +```python +class State(rx.State): + \"""The app state.\""" + + # The images to show. + img: list[str] + + async def handle_upload(self, files: list[rx.UploadFile]): + \"""Handle the upload of file(s). + + Args: + files: The uploaded files. + \""" + for file in files: + upload_data = await file.read() + outfile = rx.get_upload_dir() / file.filename + + # Save the file. + with outfile.open("wb") as file_object: + file_object.write(upload_data) + + # Update the img var. + self.img.append(file.filename) + + +color = "rgb(107,99,246)" + + +def index(): + \"""The main view.\""" + return rx.vstack( + rx.upload( + rx.vstack( + rx.button("Select File", color=color, bg="white", border=f"1px solid \{color}"), + rx.text("Drag and drop files here or click to select files"), + ), + id="upload2", + multiple=True, + accept = { + "application/pdf": [".pdf"], + "image/png": [".png"], + "image/jpeg": [".jpg", ".jpeg"], + "image/gif": [".gif"], + "image/webp": [".webp"], + "text/html": [".html", ".htm"], + }, + max_files=5, + disabled=False, + on_keyboard=True, + border=f"1px dotted \{color}", + padding="5em", + ), + rx.button( + "Upload", + on_click=State.handle_upload(rx.upload_files(upload_id="upload2")), + ), + rx.chakra.responsive_grid( + rx.foreach( + State.img, + lambda img: rx.vstack( + rx.image(src=rx.get_upload_url(img)), + rx.text(img), + ), + ), + columns=[2], + spacing="5px", + ), + padding="5em", + ) +``` + +## Handling the Upload + +Your event handler should be an async function that accepts a single argument, +`files: list[UploadFile]`, which will contain [FastAPI UploadFile](https://fastapi.tiangolo.com/tutorial/request-files) instances. +You can read the files and save them anywhere as shown in the example. + +In your UI, you can bind the event handler to a trigger, such as a button `on_click` event, and pass in the files using `rx.upload_files()`. + +### Saving the File + +By convention, Reflex provides the function `rx.get_upload_dir()` to get the directory where uploaded files may be saved. The upload dir comes from the environment variable `REFLEX_UPLOADED_FILES_DIR`, or `./uploaded_files` if not specified. + +The backend of your app will mount this uploaded files directory on `/_upload` without restriction. Any files uploaded via this mechanism will automatically be publicly accessible. To get the URL for a file inside the upload dir, use the `rx.get_upload_url(filename)` function in a frontend component. + +```md alert +When using the Reflex hosting service, the uploaded files directory is not persistent and will be cleared on every deployment. + +For persistent storage of uploaded files, it is recommended to use an external service, such as S3. +``` + +## Cancellation + +The `id` provided to the `rx.upload` component can be passed to the special event handler `rx.cancel_upload(id)` to stop uploading on demand. Cancellation can be triggered directly by a frontend event trigger, or it can be returned from a backend event handler. + +## Progress + +The `rx.upload_files` special event arg also accepts an `on_upload_progress` event trigger which will be fired about every second during the upload operation to report the progress of the upload. This can be used to update a progress bar or other UI elements to show the user the progress of the upload. + +```python +class UploadExample(rx.State): + uploading: bool = False + progress: int = 0 + total_bytes: int = 0 + + async def handle_upload(self, files: list[rx.UploadFile]): + for file in files: + self.total_bytes += len(await file.read()) + + def handle_upload_progress(self, progress: dict): + self.uploading = True + self.progress = round(progress["progress"] * 100) + if self.progress >= 100: + self.uploading = False + + def cancel_upload(self): + self.uploading = False + return rx.cancel_upload("upload3") + + +def upload_form(): + return rx.vstack( + rx.upload( + rx.text("Drag and drop files here or click to select files"), + id="upload3", + border="1px dotted rgb(107,99,246)", + padding="5em", + ), + rx.vstack(rx.foreach(rx.selected_files("upload3"), rx.text)), + rx.progress(value=UploadExample.progress, max=100), + rx.cond( + ~UploadExample.uploading, + rx.button( + "Upload", + on_click=UploadExample.handle_upload( + rx.upload_files( + upload_id="upload3", + on_upload_progress=UploadExample.handle_upload_progress, + ), + ), + ), + rx.button("Cancel", on_click=UploadExample.cancel_upload), + ), + rx.text("Total bytes uploaded: ", UploadExample.total_bytes), + align="center", + ) +``` + +The `progress` dictionary contains the following keys: + +```javascript +\{ + 'loaded': 36044800, + 'total': 54361908, + 'progress': 0.6630525183185255, + 'bytes': 20447232, + 'rate': None, + 'estimated': None, + 'event': \{'isTrusted': True}, + 'upload': True +} +``` \ No newline at end of file diff --git a/docs/library/graphing/areachart.md b/docs/library/graphing/areachart.md new file mode 100644 index 000000000..0495bda8a --- /dev/null +++ b/docs/library/graphing/areachart.md @@ -0,0 +1,247 @@ +--- +components: + - rx.recharts.AreaChart + - rx.recharts.Area +--- + +# Area Chart + +```python exec +import reflex as rx +from pcweb.templates.docpage import docdemo, docgraphing +import random + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +range_data = [ + { + "day": "05-01", + "temperature": [ + -1, + 10 + ] + }, + { + "day": "05-02", + "temperature": [ + 2, + 15 + ] + }, + { + "day": "05-03", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-04", + "temperature": [ + 4, + 12 + ] + }, + { + "day": "05-05", + "temperature": [ + 12, + 16 + ] + }, + { + "day": "05-06", + "temperature": [ + 5, + 16 + ] + }, + { + "day": "05-07", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-08", + "temperature": [ + 0, + 8 + ] + }, + { + "day": "05-09", + "temperature": [ + -3, + 5 + ] + } +] + + +area_chart_state = """class AreaState(rx.State): + data=data + + def randomize_data(self): + for i in range(len(self.data)): + self.data[i]["uv"] = random.randint(0, 10000) + self.data[i]["pv"] = random.randint(0, 10000) + self.data[i]["amt"] = random.randint(0, 10000) + + +""" +exec(area_chart_state) + + +area_chart_example = """rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data)""" + +area_chart_example_2 = """rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.area( + data_key="pv", + stroke="#82ca9d", + fill="#82ca9d" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data)""" + + +range_area_chart = """rx.recharts.area_chart( + rx.recharts.area( + data_key="temperature", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.x_axis(data_key="day"), + rx.recharts.y_axis(), + data=range_data)""" + + +area_chart_example_with_state = """rx.recharts.area_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8", + type_="natural", + on_click=AreaState.randomize_data, + + ), + rx.recharts.area( + data_key="pv", + stroke="#82ca9d", + fill="#82ca9d", + type_="natural", + ), + rx.recharts.x_axis( + data_key="name", + ), + rx.recharts.y_axis(), + rx.recharts.legend(), + rx.recharts.cartesian_grid( + stroke_dasharray="3 3", + ), + data=AreaState.data, + width="100%", + height=400, + ) +""" +``` + +An area chart combines the line chart and bar chart to show how one or more groups’ numeric values change over the progression of a second variable, typically that of time. An area chart is distinguished from a line chart by the addition of shading between lines and a baseline, like in a bar chart. + +For an area chart we must define an `rx.recharts.area()` component that has a `data_key` which clearly states which variable in our data we are tracking. In this simple example we track `uv` against `name` and therefore set the `rx.recharts.x_axis` to equal `name`. + +```python eval +docgraphing( + area_chart_example, + comp = eval(area_chart_example), + data = "data=" + str(data) +) +``` + +Multiple areas can be placed on the same `area_chart`. + +```python eval +docgraphing( + area_chart_example_2, + comp = eval(area_chart_example_2), + data = "data=" + str(data) +) +``` + +You can also assign a range in the area by assiging the data_key in the `rx.recharts.area` to a list with two elements, i.e. here a range of two temperatures for each date. + +```python eval +docgraphing( + area_chart_example_2, + comp = eval(range_area_chart), + data = "data=" + str(range_data) +) +``` + +Here is an example of an area graph with a `State`. Here we have defined a function `randomize_data`, which randomly changes the data for both graphs when the first defined `area` is clicked on using `on_click=AreaState.randomize_data`. + +```python eval +docdemo(area_chart_example_with_state, + state=area_chart_state, + comp=eval(area_chart_example_with_state), + context=True, +) +``` diff --git a/docs/library/graphing/axis.md b/docs/library/graphing/axis.md new file mode 100644 index 000000000..26a12013b --- /dev/null +++ b/docs/library/graphing/axis.md @@ -0,0 +1,10 @@ +--- +components: + - rx.recharts.XAxis + - rx.recharts.YAxis + - rx.recharts.ZAxis +--- + +# Axis + +The X-, Y-, Z-axes. diff --git a/docs/library/graphing/barchart.md b/docs/library/graphing/barchart.md new file mode 100644 index 000000000..3342f49d6 --- /dev/null +++ b/docs/library/graphing/barchart.md @@ -0,0 +1,245 @@ +--- +components: + - rx.recharts.BarChart + - rx.recharts.RadialBarChart + - rx.recharts.Bar +--- + +# Bar Chart + +```python exec +import reflex as rx +from pcweb.templates.docpage import docdemo, docgraphing +import random + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +range_data = [ + { + "day": "05-01", + "temperature": [ + -1, + 10 + ] + }, + { + "day": "05-02", + "temperature": [ + 2, + 15 + ] + }, + { + "day": "05-03", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-04", + "temperature": [ + 4, + 12 + ] + }, + { + "day": "05-05", + "temperature": [ + 12, + 16 + ] + }, + { + "day": "05-06", + "temperature": [ + 5, + 16 + ] + }, + { + "day": "05-07", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-08", + "temperature": [ + 0, + 8 + ] + }, + { + "day": "05-09", + "temperature": [ + -3, + 5 + ] + } +] + +bar_chart_state = """class BarState(rx.State): + data=data + + def randomize_data(self): + for i in range(len(self.data)): + self.data[i]["uv"] = random.randint(0, 10000) + self.data[i]["pv"] = random.randint(0, 10000) + self.data[i]["amt"] = random.randint(0, 10000) + + +""" +exec(bar_chart_state) + +bar_chart_example = """rx.recharts.bar_chart( + rx.recharts.bar( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data)""" + +bar_chart_example_2 = """rx.recharts.bar_chart( + rx.recharts.bar( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + data_key="pv", + stroke="#82ca9d", + fill="#82ca9d" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data)""" + + +range_bar_chart = """rx.recharts.bar_chart( + rx.recharts.bar( + data_key="temperature", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.x_axis(data_key="day"), + rx.recharts.y_axis(), + data=range_data)""" + +bar_chart_example_with_state = """rx.recharts.bar_chart( + rx.recharts.bar( + data_key="uv", + stroke="#8884d8", + fill="#8884d8", + type_="natural", + on_click=BarState.randomize_data, + + ), + rx.recharts.bar( + data_key="pv", + stroke="#82ca9d", + fill="#82ca9d", + type_="natural", + ), + rx.recharts.x_axis( + data_key="name", + ), + rx.recharts.y_axis(), + rx.recharts.legend(), + rx.recharts.cartesian_grid( + stroke_dasharray="3 3", + ), + data=BarState.data, + width="100%", + height=400, + ) +""" +``` + +A bar chart presents categorical data with rectangular bars with heights or lengths proportional to the values that they represent. + +For a bar chart we must define an `rx.recharts.bar()` component for each set of values we wish to plot. Each `rx.recharts.bar()` component has a `data_key` which clearly states which variable in our data we are tracking. In this simple example we plot `uv` as a bar against the `name` column which we set as the `data_key` in `rx.recharts.x_axis`. + +```python eval +docgraphing( + bar_chart_example, + comp = eval(bar_chart_example), + data = "data=" + str(data) +) +``` + +Multiple bars can be placed on the same `bar_chart`, using multiple `rx.recharts.bar()` components. + +```python eval +docgraphing( + bar_chart_example_2, + comp = eval(bar_chart_example_2), + data = "data=" + str(data) +) +``` + +You can also assign a range in the bar by assiging the data_key in the `rx.recharts.bar` to a list with two elements, i.e. here a range of two temperatures for each date. + +```python eval +docgraphing( + range_bar_chart, + comp = eval(range_bar_chart), + data = "data=" + str(range_data) +) +``` + +Here is an example of a bar graph with a `State`. Here we have defined a function `randomize_data`, which randomly changes the data for both graphs when the first defined `bar` is clicked on using `on_click=BarState.randomize_data`. + +```python eval +docdemo(bar_chart_example_with_state, + state=bar_chart_state, + comp=eval(bar_chart_example_with_state), + context=True, +) +``` diff --git a/docs/library/graphing/brush.md b/docs/library/graphing/brush.md new file mode 100644 index 000000000..63a44403e --- /dev/null +++ b/docs/library/graphing/brush.md @@ -0,0 +1,81 @@ +--- +components: + - rx.recharts.Brush +--- + +# Brush + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { "name": '1', "uv": 300, "pv": 456 }, + { "name": '2', "uv": -145, "pv": 230 }, + { "name": '3', "uv": -100, "pv": 345 }, + { "name": '4', "uv": -8, "pv": 450 }, + { "name": '5', "uv": 100, "pv": 321 }, + { "name": '6', "uv": 9, "pv": 235 }, + { "name": '7', "uv": 53, "pv": 267 }, + { "name": '8', "uv": 252, "pv": -378 }, + { "name": '9', "uv": 79, "pv": -210 }, + { "name": '10', "uv": 294, "pv": -23 }, + { "name": '12', "uv": 43, "pv": 45 }, + { "name": '13', "uv": -74, "pv": 90 }, + { "name": '14', "uv": -71, "pv": 130 }, + { "name": '15', "uv": -117, "pv": 11 }, + { "name": '16', "uv": -186, "pv": 107 }, + { "name": '17', "uv": -16, "pv": 926 }, + { "name": '18', "uv": -125, "pv": 653 }, + { "name": '19', "uv": 222, "pv": 366 }, + { "name": '20', "uv": 372, "pv": 486 }, + { "name": '21', "uv": 182, "pv": 512 }, + { "name": '22', "uv": 164, "pv": 302 }, + { "name": '23', "uv": 316, "pv": 425 }, + { "name": '24', "uv": 131, "pv": 467 }, + { "name": '25', "uv": 291, "pv": -190 }, + { "name": '26', "uv": -47, "pv": 194 }, + { "name": '27', "uv": -415, "pv": 371 }, + { "name": '28', "uv": -182, "pv": 376 }, + { "name": '29', "uv": -93, "pv": 295 }, + { "name": '30', "uv": -99, "pv": 322 }, + { "name": '31', "uv": -52, "pv": 246 }, + { "name": '32', "uv": 154, "pv": 33 }, + { "name": '33', "uv": 205, "pv": 354 }, + { "name": '34', "uv": 70, "pv": 258 }, + { "name": '35', "uv": -25, "pv": 359 }, + { "name": '36', "uv": -59, "pv": 192 }, + { "name": '37', "uv": -63, "pv": 464 }, + { "name": '38', "uv": -91, "pv": -2 }, + { "name": '39', "uv": -66, "pv": 154 }, + { "name": '40', "uv": -50, "pv": 186 }, +] + + + +brush_example = """rx.recharts.bar_chart( + rx.recharts.bar( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + data_key="pv", + stroke="#82ca9d", + fill="#82ca9d" + ), + rx.recharts.brush(data_key="name", height=30, stroke="#8884d8"), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data)""" +``` + +The brush component allows us to view charts that have a large number of data points. So to view and analyze them efficiently, there is a slider down them that helps the viewer to select some data points that the viewer needs to be displayed. + +```python eval +docgraphing( + brush_example, + comp = eval(brush_example), + data = "data=" + str(data) +) +``` diff --git a/docs/library/graphing/cartesianaxisgrid.md b/docs/library/graphing/cartesianaxisgrid.md new file mode 100644 index 000000000..25a5731d2 --- /dev/null +++ b/docs/library/graphing/cartesianaxisgrid.md @@ -0,0 +1,157 @@ +--- +components: + - rx.recharts.CartesianGrid + - rx.recharts.CartesianAxis +--- + +# Cartesian Grid + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +range_data = [ + { + "day": "05-01", + "temperature": [ + -1, + 10 + ] + }, + { + "day": "05-02", + "temperature": [ + 2, + 15 + ] + }, + { + "day": "05-03", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-04", + "temperature": [ + 4, + 12 + ] + }, + { + "day": "05-05", + "temperature": [ + 12, + 16 + ] + }, + { + "day": "05-06", + "temperature": [ + 5, + 16 + ] + }, + { + "day": "05-07", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-08", + "temperature": [ + 0, + 8 + ] + }, + { + "day": "05-09", + "temperature": [ + -3, + 5 + ] + } +] + + + +composed_chart_example = """rx.recharts.composed_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + data_key="amt", + bar_size=20, + fill="#413ea0" + ), + rx.recharts.line( + data_key="pv", + type_="monotone", + stroke="#ff7300" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.cartesian_grid(stroke_dasharray="3 3"), + rx.recharts.graphing_tooltip(), + data=data)""" +``` + +A cartesian axis adds in reference axes to the cartesian graphs. + +```python eval +docgraphing( + composed_chart_example, + comp = eval(composed_chart_example), + data = "data=" + str(data) +) +``` diff --git a/docs/library/graphing/composedchart.md b/docs/library/graphing/composedchart.md new file mode 100644 index 000000000..7401bcbd9 --- /dev/null +++ b/docs/library/graphing/composedchart.md @@ -0,0 +1,156 @@ +--- +components: + - rx.recharts.ComposedChart +--- + +# Composed Chart + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +range_data = [ + { + "day": "05-01", + "temperature": [ + -1, + 10 + ] + }, + { + "day": "05-02", + "temperature": [ + 2, + 15 + ] + }, + { + "day": "05-03", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-04", + "temperature": [ + 4, + 12 + ] + }, + { + "day": "05-05", + "temperature": [ + 12, + 16 + ] + }, + { + "day": "05-06", + "temperature": [ + 5, + 16 + ] + }, + { + "day": "05-07", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-08", + "temperature": [ + 0, + 8 + ] + }, + { + "day": "05-09", + "temperature": [ + -3, + 5 + ] + } +] + + + +composed_chart_example = """rx.recharts.composed_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + data_key="amt", + bar_size=20, + fill="#413ea0" + ), + rx.recharts.line( + data_key="pv", + type_="monotone", + stroke="#ff7300" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.cartesian_grid(stroke_dasharray="3 3"), + rx.recharts.graphing_tooltip(), + data=data)""" +``` + +A `composed_chart` is a chart that is composed of multiple charts. The charts are placed on top of each other. The charts are placed in the order they are given in the `composed_chart` function. + +```python eval +docgraphing( + composed_chart_example, + comp = eval(composed_chart_example), + data = "data=" + str(data) +) +``` diff --git a/docs/library/graphing/errorbar.md b/docs/library/graphing/errorbar.md new file mode 100644 index 000000000..ff3770430 --- /dev/null +++ b/docs/library/graphing/errorbar.md @@ -0,0 +1,105 @@ +--- +components: + - rx.recharts.ErrorBar +--- + +# Error Bar + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { + "x": 45, + "y": 100, + "z": 150, + "errorY": [ + 30, + 20 + ], + "errorX": 5 + }, + { + "x": 100, + "y": 200, + "z": 200, + "errorY": [ + 20, + 30 + ], + "errorX": 3 + }, + { + "x": 120, + "y": 100, + "z": 260, + "errorY": 20, + "errorX": [ + 5, + 3 + ] + }, + { + "x": 170, + "y": 300, + "z": 400, + "errorY": [ + 15, + 18 + ], + "errorX": 4 + }, + { + "x": 140, + "y": 250, + "z": 280, + "errorY": 23, + "errorX": [ + 6, + 7 + ] + }, + { + "x": 150, + "y": 400, + "z": 500, + "errorY": [ + 21, + 10 + ], + "errorX": 4 + }, + { + "x": 110, + "y": 280, + "z": 200, + "errorY": 21, + "errorX": [ + 5, + 6 + ] + } +] + + + +scatter_chart_simple_example = """rx.recharts.scatter_chart( + rx.recharts.scatter( + rx.recharts.error_bar(data_key="errorY", direction="y", width=4, stroke_width=2, stroke="red"), + rx.recharts.error_bar(data_key="errorX", direction="x", width=4, stroke_width=2), + data=data, + fill="#8884d8", + name="A"), + rx.recharts.x_axis(data_key="x", name="x"), + rx.recharts.y_axis(data_key="y", name="y"), + rx.recharts.graphing_tooltip(), + rx.recharts.legend(), + )""" +``` + +An error bar is a line through a point on a graph, parallel to one of the axes, which represents the uncertainty or variation of the corresponding coordinate of the point. + +```python eval +docgraphing(scatter_chart_simple_example, comp=eval(scatter_chart_simple_example), data = "data=" + str(data)) +``` diff --git a/docs/library/graphing/funnelchart.md b/docs/library/graphing/funnelchart.md new file mode 100644 index 000000000..818dcc31e --- /dev/null +++ b/docs/library/graphing/funnelchart.md @@ -0,0 +1,94 @@ +--- +components: + - rx.recharts.FunnelChart + - rx.recharts.Funnel +--- + +# Funnel Chart + +```python exec +import reflex as rx +from pcweb.templates.docpage import docdemo, docgraphing +import random + +data = [ + { + "value": 100, + "name": "Sent", + "fill": "#8884d8" + }, + { + "value": 80, + "name": "Viewed", + "fill": "#83a6ed" + }, + { + "value": 50, + "name": "Clicked", + "fill": "#8dd1e1" + }, + { + "value": 40, + "name": "Add to Cart", + "fill": "#82ca9d" + }, + { + "value": 26, + "name": "Purchased", + "fill": "#a4de6c" + } +] + +funnel_chart_state = """class FunnelState(rx.State): + data=data + + def randomize_data(self): + self.data[0]["value"] = 100 + for i in range(len(self.data)-1): + self.data[i+1]["value"] = self.data[i]["value"] - random.randint(0, 20) +""" +exec(funnel_chart_state) + +funnel_chart_example = """rx.recharts.funnel_chart( + rx.recharts.funnel( + rx.recharts.label_list(position="right", data_key="name", fill="#000", stroke="none"), + rx.recharts.label_list(position="right", data_key="name", fill="#000", stroke="none"), + data_key="value", + data=data + ), + rx.recharts.graphing_tooltip(), + width=730, + height=250)""" + +funnel_chart_example_with_state = """rx.recharts.funnel_chart( + rx.recharts.funnel( + rx.recharts.label_list(position="right", data_key="name", fill="#000", stroke="none"), + data_key="value", + data=FunnelState.data, + on_click=FunnelState.randomize_data, + ), + rx.recharts.graphing_tooltip(), + width=1000, + height=250)""" + +``` + +A funnel chart is a graphical representation used to visualize how data moves through a process. In a funnel chart, the dependent variable’s value diminishes in the subsequent stages of the process. It can be used to demonstrate the flow of users through for example a business or sales process. + +```python eval +docgraphing( + funnel_chart_example, + comp = eval(funnel_chart_example), + data = "data=" + str(data) +) +``` + +Here is an example of a funnel chart with a `State`. Here we have defined a function `randomize_data`, which randomly changes the data when the graph is clicked on using `on_click=FunnelState.randomize_data`. + +```python eval +docdemo(funnel_chart_example_with_state, + state=funnel_chart_state, + comp=eval(funnel_chart_example_with_state), + context=True, +) +``` diff --git a/docs/library/graphing/label.md b/docs/library/graphing/label.md new file mode 100644 index 000000000..98d11f7bd --- /dev/null +++ b/docs/library/graphing/label.md @@ -0,0 +1,91 @@ +--- +components: + - rx.recharts.Label + - rx.recharts.LabelList +--- + +# Label + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 5800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + + + +brush_example = """rx.recharts.bar_chart( + rx.recharts.bar( + rx.recharts.label_list(data_key="uv", position="top"), + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + + ), + rx.recharts.bar( + rx.recharts.label_list(data_key="pv", position="top"), + data_key="pv", + stroke="#82ca9d", + fill="#82ca9d" + ), + rx.recharts.x_axis( + data_key="name" + ), + rx.recharts.y_axis(), + margin={"left": 10, "right": 0, "top": 20, "bottom": 10}, + + data=data)""" +``` + +`rx.recharts.label`and `rx.recharts.label_list` add in labels to the graphs. `rx.recharts.label_list` takes in a `data_key` where we define the data column to plot. + +```python eval +docgraphing( + brush_example, + comp = eval(brush_example), + data = "data=" + str(data) +) +``` diff --git a/docs/library/graphing/legend.md b/docs/library/graphing/legend.md new file mode 100644 index 000000000..6477a1dc1 --- /dev/null +++ b/docs/library/graphing/legend.md @@ -0,0 +1,157 @@ +--- +components: + - rx.recharts.Legend +--- + +# Legend + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +range_data = [ + { + "day": "05-01", + "temperature": [ + -1, + 10 + ] + }, + { + "day": "05-02", + "temperature": [ + 2, + 15 + ] + }, + { + "day": "05-03", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-04", + "temperature": [ + 4, + 12 + ] + }, + { + "day": "05-05", + "temperature": [ + 12, + 16 + ] + }, + { + "day": "05-06", + "temperature": [ + 5, + 16 + ] + }, + { + "day": "05-07", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-08", + "temperature": [ + 0, + 8 + ] + }, + { + "day": "05-09", + "temperature": [ + -3, + 5 + ] + } +] + + + +composed_chart_example = """rx.recharts.composed_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + data_key="amt", + bar_size=20, + fill="#413ea0" + ), + rx.recharts.line( + data_key="pv", + type_="monotone", + stroke="#ff7300" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.cartesian_grid(stroke_dasharray="3 3"), + rx.recharts.graphing_tooltip(), + rx.recharts.legend(), + data=data)""" +``` + +A legend tells what each plot represents. Just like on a map, the legend helps the reader understand what they are looking at. For a line graph for example it tells us what each line represents. + +```python eval +docgraphing( + composed_chart_example, + comp = eval(composed_chart_example), + data = "data=" + str(data) +) +``` diff --git a/docs/library/graphing/linechart.md b/docs/library/graphing/linechart.md new file mode 100644 index 000000000..8a7db97ee --- /dev/null +++ b/docs/library/graphing/linechart.md @@ -0,0 +1,169 @@ +--- +components: + - rx.recharts.LineChart + - rx.recharts.Line +--- + +# Line Chart + +```python exec +import random +from typing import Any + +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + + +line_chart_simple_example = """rx.recharts.line_chart( + rx.recharts.line( + data_key="pv", + stroke="#8884d8",), + rx.recharts.line( + data_key="uv", + stroke="#82ca9d",), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=data + )""" + +line_chart_complex_example = """rx.recharts.line_chart( + rx.recharts.line( + data_key="pv", + type_="monotone", + stroke="#8884d8",), + rx.recharts.line( + data_key="uv", + type_="monotone", + stroke="#82ca9d",), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.cartesian_grid(stroke_dasharray="3 3"), + rx.recharts.graphing_tooltip(), + rx.recharts.legend(), + data=data + )""" +``` + +A line chart is a type of chart used to show information that changes over time. Line charts are created by plotting a series of several points and connecting them with a straight line. + +For a line chart we must define an `rx.recharts.line()` component for each set of values we wish to plot. Each `rx.recharts.line()` component has a `data_key` which clearly states which variable in our data we are tracking. In this simple example we plot `pv` and `uv` as separate lines against the `name` column which we set as the `data_key` in `rx.recharts.x_axis`. + +```python eval +docgraphing(line_chart_simple_example, comp=eval(line_chart_simple_example), data = "data=" + str(data)) +``` + +## Chart Features + +Our second example uses exactly the same data as our first example, except now we add some extra features to our line graphs. We add a `type_` prop to `rx.recharts.line` to style the lines differently. In addition, we add an `rx.recharts.cartesian_grid` to get a grid in the background, an `rx.recharts.legend` to give us a legend for our graphs and an `rx.recharts.graphing_tooltip` to add a box with text that appears when you pause the mouse pointer on an element in the graph. + +```python eval +docgraphing(line_chart_complex_example, comp=eval(line_chart_complex_example), data = "data=" + str(data)) +``` + +## Dynamic Data and Style + +```python exec +initial_data = data + + +class LineChartState(rx.State): + data: list[dict[str, Any]] = initial_data + pv_type: str = "monotone" + uv_type: str = "monotone" + + def munge_data(self): + for row in self.data: + row["uv"] += random.randint(-500, 500) + row["pv"] += random.randint(-1000, 1000) + + +line_chart_state_example = """rx.vstack( + rx.recharts.line_chart( + rx.recharts.line( + data_key="pv", + type_=LineChartState.pv_type, + stroke="#8884d8", + ), + rx.recharts.line( + data_key="uv", + type_=LineChartState.uv_type, + stroke="#82ca9d", + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + data=LineChartState.data, + ), + rx.button("Munge Data", on_click=LineChartState.munge_data), + rx.select( + ["monotone", "linear", "step", "stepBefore", "stepAfter"], + value=LineChartState.pv_type, + on_change=LineChartState.set_pv_type + ), + rx.select( + ["monotone", "linear", "step", "stepBefore", "stepAfter"], + value=LineChartState.uv_type, + on_change=LineChartState.set_uv_type + ), + height="15em", + width="100%", + )""" +``` + +Chart data can be modified by tying the `data` prop to a State var. Most other +props, such as `type_`, can be controlled dynamically as well. In the following +example the "Munge Data" button can be used to randomly modify the data, and the +two `select` elements change the line `type_`. Since the data and style is saved +in the per-browser-tab State, the changes will not be visible to other visitors. + +```python eval +docgraphing( + line_chart_state_example, + comp=eval(line_chart_state_example), + # data="initial_data=" + str(data) + "\n\n" + inspect.getsource(LineChartState), +) +``` diff --git a/docs/library/graphing/piechart.md b/docs/library/graphing/piechart.md new file mode 100644 index 000000000..577d6c474 --- /dev/null +++ b/docs/library/graphing/piechart.md @@ -0,0 +1,188 @@ +--- +components: + - rx.recharts.PieChart +--- + +# Pie Chart + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data01 = [ + { + "name": "Group A", + "value": 400 + }, + { + "name": "Group B", + "value": 300 + }, + { + "name": "Group C", + "value": 300 + }, + { + "name": "Group D", + "value": 200 + }, + { + "name": "Group E", + "value": 278 + }, + { + "name": "Group F", + "value": 189 + } +] +data02 = [ + { + "name": "Group A", + "value": 2400 + }, + { + "name": "Group B", + "value": 4567 + }, + { + "name": "Group C", + "value": 1398 + }, + { + "name": "Group D", + "value": 9800 + }, + { + "name": "Group E", + "value": 3908 + }, + { + "name": "Group F", + "value": 4800 + } +] + +pie_chart_simple_example = """rx.recharts.pie_chart( + rx.recharts.pie( + data=data01, + data_key="value", + name_key="name", + cx="50%", + cy="50%", + fill="#8884d8", + label=True, + ) + )""" + +pie_chart_complex_example = """rx.recharts.pie_chart( + rx.recharts.pie( + data=data01, + data_key="value", + name_key="name", + cx="50%", + cy="50%", + fill="#82ca9d", + inner_radius="60%", + ), + rx.recharts.pie( + data=data02, + data_key="value", + name_key="name", + cx="50%", + cy="50%", + fill="#8884d8", + outer_radius="50%", + ), + rx.recharts.graphing_tooltip(), + )""" + +``` + +A pie chart is a circular statistical graphic which is divided into slices to illustrate numerical proportion. + +For a pie chart we must define an `rx.recharts.pie()` component for each set of values we wish to plot. Each `rx.recharts.pie()` component has a `data`, a `data_key` and a `name_key` which clearly states which data and which variables in our data we are tracking. In this simple example we plot `value` column as our `data_key` against the `name` column which we set as our `name_key`. + +```python eval +docgraphing(pie_chart_simple_example, comp=eval(pie_chart_simple_example), data = "data01=" + str(data01)) +``` + +We can also add two pies on one chart by using two `rx.recharts.pie` components. + +```python eval +docgraphing(pie_chart_complex_example, comp=eval(pie_chart_complex_example), data = "data01=" + str(data01) + "&data02=" + str(data02)) +``` + +# Dynamic Data + +Chart data tied to a State var causes the chart to automatically update when the +state changes, providing a nice way to visualize data in response to user +interface elements. View the "Data" tab to see the substate driving this +half-pie chart. + +```python exec +from typing import Any + + +class PieChartState(rx.State): + resources: list[dict[str, Any]] = [ + dict(type_="🏆", count=1), + dict(type_="🪵", count=1), + dict(type_="🥑", count=1), + dict(type_="🧱", count=1), + ] + + @rx.cached_var + def resource_types(self) -> list[str]: + return [r["type_"] for r in self.resources] + + def increment(self, type_: str): + for resource in self.resources: + if resource["type_"] == type_: + resource["count"] += 1 + break + + def decrement(self, type_: str): + for resource in self.resources: + if resource["type_"] == type_ and resource["count"] > 0: + resource["count"] -= 1 + break + + +pie_chart_state_example = """ +rx.hstack( + rx.recharts.pie_chart( + rx.recharts.pie( + data=PieChartState.resources, + data_key="count", + name_key="type_", + cx="50%", + cy="50%", + start_angle=180, + end_angle=0, + fill="#8884d8", + label=True, + ), + rx.recharts.graphing_tooltip(), + ), + rx.vstack( + rx.foreach( + PieChartState.resource_types, + lambda type_, i: rx.hstack( + rx.button("-", on_click=PieChartState.decrement(type_)), + rx.text(type_, PieChartState.resources[i]["count"]), + rx.button("+", on_click=PieChartState.increment(type_)), + ), + ), + ), + width="100%", + height="15em", +)""" +``` + +```python eval +docgraphing( + pie_chart_state_example, + comp=eval(pie_chart_state_example), + # data=inspect.getsource(PieChartState), +) +``` diff --git a/docs/library/graphing/plotly.md b/docs/library/graphing/plotly.md new file mode 100644 index 000000000..372640d7a --- /dev/null +++ b/docs/library/graphing/plotly.md @@ -0,0 +1,113 @@ +--- +components: + - rx.plotly +--- + +# Plotly + +```python exec +import reflex as rx +import pandas as pd +import plotly.express as px +import plotly.graph_objects as go + +def line(): + df = pd.read_csv("data/canada_life.csv") + line = px.line(df, x="year", y="lifeExp", title="Life expectancy in Canada") + return line + + +# Read data from a csv +def mount(): + z_data = pd.read_csv("data/mt_bruno_elevation.csv") + mount = go.Figure(data=[go.Surface(z=z_data.values)]) + mount.update_traces( + contours_z=dict( + show=True, usecolormap=True, highlightcolor="limegreen", project_z=True + ) + ) + mount.update_layout( + scene_camera_eye=dict(x=1.87, y=0.88, z=-0.64), + width=500, + height=500, + margin=dict(l=65, r=50, b=65, t=90), + ) + return mount + + +style = { + "box": { + "borderRadius": "1em", + "bg": "white", + "boxShadow": "rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px", + "padding": 5, + "width": "100%", + "overflow_x": "auto", + } +} +``` + +Plotly is a graphing library that can be used to create interactive graphs. + +Let's create a line graph of life expectancy in Canada as an example. + +```python eval +rx.center( + rx.plotly(data=line()), + height="400px", + style=style["box"], +) +``` + +First create a plotly figure. In this example we use plotly express to do so. + +```python +import plotly.express as px + +df = px.data.gapminder().query("country=='Canada'") +fig = px.line(df, x="year", y="lifeExp", title='Life expectancy in Canada') +``` + +Now pass the plotly figure to the Plotly component. + +```python +rx.plotly(data=fig, height="400px") +``` + +Now lets take a look at a more complex example. +Let's create a 3D surface plot of Mount Bruno. + +```python eval +rx.center( + rx.vstack( + rx.plotly(data=mount()), + ), + height="400px", + style=style["box"], +) +``` + +Read in the Mount Bruno data as a csv and create a plotly figure. + +```python +import plotly.graph_objects as go + +import pandas as pd + +# Read data from a csv +z_data = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv') + +fig = go.Figure(data=[go.Surface(z=z_data.values)]) +fig.update_traces(contours_z=dict(show=True, usecolormap=True, + highlightcolor="limegreen", project_z=True)) +fig.update_layout(scene_camera_eye=dict(x=1.87, y=0.88, z=-0.64), + width=500, height=500, + margin=dict(l=65, r=50, b=65, t=90) +) +``` + +Now pass the plotly figure again to the plotly component. + +```python +rx.plotly(data=fig, height="400px") +``` diff --git a/docs/library/graphing/radarchart.md b/docs/library/graphing/radarchart.md new file mode 100644 index 000000000..b02a1a304 --- /dev/null +++ b/docs/library/graphing/radarchart.md @@ -0,0 +1,173 @@ +--- +components: + - rx.recharts.RadarChart +--- + +# Radar Chart + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { + "subject": "Math", + "A": 120, + "B": 110, + "fullMark": 150 + }, + { + "subject": "Chinese", + "A": 98, + "B": 130, + "fullMark": 150 + }, + { + "subject": "English", + "A": 86, + "B": 130, + "fullMark": 150 + }, + { + "subject": "Geography", + "A": 99, + "B": 100, + "fullMark": 150 + }, + { + "subject": "Physics", + "A": 85, + "B": 90, + "fullMark": 150 + }, + { + "subject": "History", + "A": 65, + "B": 85, + "fullMark": 150 + } +] + +radar_chart_simple_example = """rx.recharts.radar_chart( + rx.recharts.radar( + data_key="A", + stroke="#8884d8", + fill="#8884d8", + ), + rx.recharts.polar_grid(), + rx.recharts.polar_angle_axis(data_key="subject"), + data=data + )""" + +radar_chart_complex_example = """rx.recharts.radar_chart( + rx.recharts.radar( + data_key="A", + stroke="#8884d8", + fill="#8884d8", + ), + rx.recharts.radar( + data_key="B", + stroke="#82ca9d", + fill="#82ca9d", + fill_opacity=0.6, + ), + rx.recharts.polar_grid(), + rx.recharts.polar_angle_axis(data_key="subject"), + rx.recharts.legend(), + data=data + )""" + +``` + +A radar chart shows multivariate data of three or more quantitative variables mapped onto an axis. + +For a radar chart we must define an `rx.recharts.radar()` component for each set of values we wish to plot. Each `rx.recharts.radar()` component has a `data_key` which clearly states which variable in our data we are plotting. In this simple example we plot the `A` column of our data against the `subject` column which we set as the `data_key` in `rx.recharts.polar_angle_axis`. + +```python eval +docgraphing(radar_chart_simple_example, comp=eval(radar_chart_simple_example), data = "data=" + str(data)) +``` + +We can also add two radars on one chart by using two `rx.recharts.radar` components. + +```python eval +docgraphing(radar_chart_complex_example, comp=eval(radar_chart_complex_example), data = "data=" + str(data)) +``` + +# Dynamic Data + +Chart data tied to a State var causes the chart to automatically update when the +state changes, providing a nice way to visualize data in response to user +interface elements. View the "Data" tab to see the substate driving this +radar chart of character traits. + +```python exec +from typing import Any + + +class RadarChartState(rx.State): + total_points: int = 100 + traits: list[dict[str, Any]] = [ + dict(trait="Strength", value=15), + dict(trait="Dexterity", value=15), + dict(trait="Constitution", value=15), + dict(trait="Intelligence", value=15), + dict(trait="Wisdom", value=15), + dict(trait="Charisma", value=15), + ] + + @rx.var + def remaining_points(self) -> int: + return self.total_points - sum(t["value"] for t in self.traits) + + @rx.cached_var + def trait_names(self) -> list[str]: + return [t["trait"] for t in self.traits] + + def set_trait(self, trait: str, value: int): + for t in self.traits: + if t["trait"] == trait: + available_points = self.remaining_points + t["value"] + value = min(value, available_points) + t["value"] = value + break + +radar_chart_state_example = """ +rx.hstack( + rx.recharts.radar_chart( + rx.recharts.radar( + data_key="value", + stroke="#8884d8", + fill="#8884d8", + ), + rx.recharts.polar_grid(), + rx.recharts.polar_angle_axis(data_key="trait"), + data=RadarChartState.traits, + ), + rx.vstack( + rx.foreach( + RadarChartState.trait_names, + lambda trait_name, i: rx.hstack( + rx.text(trait_name, width="7em"), + rx.chakra.slider( + value=RadarChartState.traits[i]["value"].to(int), + on_change=lambda value: RadarChartState.set_trait(trait_name, value), + width="25vw", + ), + rx.text(RadarChartState.traits[i]['value']), + ), + ), + rx.text("Remaining points: ", RadarChartState.remaining_points), + ), + width="100%", + height="15em", +) +""" +``` + +```python eval +docgraphing( + radar_chart_state_example, + comp=eval(radar_chart_state_example), + # data=inspect.getsource(RadarChartState), +) +``` diff --git a/docs/library/graphing/reference.md b/docs/library/graphing/reference.md new file mode 100644 index 000000000..8b30c7e46 --- /dev/null +++ b/docs/library/graphing/reference.md @@ -0,0 +1,102 @@ +--- +components: + - rx.recharts.ReferenceLine + - rx.recharts.ReferenceDot + - rx.recharts.ReferenceArea +--- + +# Reference + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { + "x": 45, + "y": 100, + "z": 150, + "errorY": [ + 30, + 20 + ], + "errorX": 5 + }, + { + "x": 100, + "y": 200, + "z": 200, + "errorY": [ + 20, + 30 + ], + "errorX": 3 + }, + { + "x": 120, + "y": 100, + "z": 260, + "errorY": 20, + "errorX": [ + 10, + 3 + ] + }, + { + "x": 170, + "y": 300, + "z": 400, + "errorY": [ + 15, + 18 + ], + "errorX": 4 + }, + { + "x": 140, + "y": 250, + "z": 280, + "errorY": 23, + "errorX": [ + 6, + 7 + ] + }, + { + "x": 150, + "y": 400, + "z": 500, + "errorY": [ + 21, + 10 + ], + "errorX": 4 + }, + { + "x": 110, + "y": 280, + "z": 200, + "errorY": 21, + "errorX": [ + 1, + 8 + ] + } +] + + +scatter_chart_simple_example = """rx.recharts.scatter_chart( + rx.recharts.scatter( + data=data, + fill="#8884d8", + name="A"), + rx.recharts.reference_area(x1= 150, x2=180, y1=150, y2=300, fill="#8884d8", fill_opacity=0.3), + rx.recharts.x_axis(data_key="x", name="x", type_="number"), + rx.recharts.y_axis(data_key="y", name="y", type_="number"), + rx.recharts.graphing_tooltip(), + )""" +``` + +```python eval +docgraphing(scatter_chart_simple_example, comp=eval(scatter_chart_simple_example), data = "data=" + str(data)) +``` diff --git a/docs/library/graphing/scatterchart.md b/docs/library/graphing/scatterchart.md new file mode 100644 index 000000000..029176038 --- /dev/null +++ b/docs/library/graphing/scatterchart.md @@ -0,0 +1,168 @@ +--- +components: + - rx.recharts.ScatterChart + - rx.recharts.Scatter +--- + +# Scatter Chart + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data01 = [ + { + "x": 100, + "y": 200, + "z": 200 + }, + { + "x": 120, + "y": 100, + "z": 260 + }, + { + "x": 170, + "y": 300, + "z": 400 + }, + { + "x": 170, + "y": 250, + "z": 280 + }, + { + "x": 150, + "y": 400, + "z": 500 + }, + { + "x": 110, + "y": 280, + "z": 200 + } +] + +data02 = [ + { + "x": 200, + "y": 260, + "z": 240 + }, + { + "x": 240, + "y": 290, + "z": 220 + }, + { + "x": 190, + "y": 290, + "z": 250 + }, + { + "x": 198, + "y": 250, + "z": 210 + }, + { + "x": 180, + "y": 280, + "z": 260 + }, + { + "x": 210, + "y": 220, + "z": 230 + } +] + +scatter_chart_simple_example = """rx.recharts.scatter_chart( + rx.recharts.scatter( + data=data01, + fill="#8884d8",), + rx.recharts.x_axis(data_key="x", type_="number"), + rx.recharts.y_axis(data_key="y") + )""" + +scatter_chart_simple_complex = """rx.recharts.scatter_chart( + rx.recharts.scatter( + data=data01, + fill="#8884d8", + name="A" + ), + rx.recharts.scatter( + data=data02, + fill="#82ca9d", + name="B" + ), + rx.recharts.cartesian_grid(stroke_dasharray="3 3"), + rx.recharts.x_axis(data_key="x", type_="number"), + rx.recharts.y_axis(data_key="y"), + rx.recharts.z_axis(data_key="z", range=[60, 400], name="score"), + rx.recharts.legend(), + rx.recharts.graphing_tooltip(), + + )""" + +``` + +A scatter chart always has two value axes to show one set of numerical data along a horizontal (value) axis and another set of numerical values along a vertical (value) axis. The chart displays points at the intersection of an x and y numerical value, combining these values into single data points. + +For a scatter chart we must define an `rx.recharts.scatter()` component for each set of values we wish to plot. Each `rx.recharts.scatter()` component has a `data` prop which clearly states which data source we plot. We also must define `rx.recharts.x_axis()` and `rx.recharts.y_axis()` so that the graph knows what data to plot on each axis. + +```python eval +docgraphing(scatter_chart_simple_example, comp=eval(scatter_chart_simple_example), data = "data01=" + str(data01)) +``` + +We can also add two scatters on one chart by using two `rx.recharts.scatter()` components, and we can define an `rx.recharts.z_axis()` which represents a third column of data and is represented by the size of the dots in the scatter plot. + +```python eval +docgraphing(scatter_chart_simple_complex, comp=eval(scatter_chart_simple_complex), data = "data01=" + str(data01) + "&data02=" + str(data02)) +``` + +# Dynamic Data + +Chart data tied to a State var causes the chart to automatically update when the +state changes, providing a nice way to visualize data in response to user +interface elements. View the "Data" tab to see the substate driving this +calculation of iterations in the Collatz Conjecture for a given starting number. +Enter a starting number in the box below the chart to recalculate. + +```python demo graphing +class ScatterChartState(rx.State): + data: list[dict[str, int]] = [] + + def compute_collatz(self, form_data: dict) -> int: + n = int(form_data.get("start") or 1) + yield rx.set_value("start", "") + self.data = [] + for ix in range(400): + self.data.append({"x": ix, "y": n}) + if n == 1: + break + if n % 2 == 0: + n = n // 2 + else: + n = 3 * n + 1 + + +def index(): + return rx.vstack( + rx.recharts.scatter_chart( + rx.recharts.scatter( + data=ScatterChartState.data, + fill="#8884d8", + ), + rx.recharts.x_axis(data_key="x", type_="number"), + rx.recharts.y_axis(data_key="y", type_="number"), + ), + rx.form.root( + rx.chakra.input(placeholder="Enter a number", id="start"), + rx.button("Compute", type="submit"), + on_submit=ScatterChartState.compute_collatz, + ), + width="100%", + height="15em", + on_mount=ScatterChartState.compute_collatz({"start": "15"}), + ) +``` diff --git a/docs/library/graphing/tooltip.md b/docs/library/graphing/tooltip.md new file mode 100644 index 000000000..69610d5ac --- /dev/null +++ b/docs/library/graphing/tooltip.md @@ -0,0 +1,156 @@ +--- +components: + - rx.recharts.GraphingTooltip +--- + +# Tooltip + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { + "name": "Page A", + "uv": 4000, + "pv": 2400, + "amt": 2400 + }, + { + "name": "Page B", + "uv": 3000, + "pv": 1398, + "amt": 2210 + }, + { + "name": "Page C", + "uv": 2000, + "pv": 9800, + "amt": 2290 + }, + { + "name": "Page D", + "uv": 2780, + "pv": 3908, + "amt": 2000 + }, + { + "name": "Page E", + "uv": 1890, + "pv": 4800, + "amt": 2181 + }, + { + "name": "Page F", + "uv": 2390, + "pv": 3800, + "amt": 2500 + }, + { + "name": "Page G", + "uv": 3490, + "pv": 4300, + "amt": 2100 + } +] + +range_data = [ + { + "day": "05-01", + "temperature": [ + -1, + 10 + ] + }, + { + "day": "05-02", + "temperature": [ + 2, + 15 + ] + }, + { + "day": "05-03", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-04", + "temperature": [ + 4, + 12 + ] + }, + { + "day": "05-05", + "temperature": [ + 12, + 16 + ] + }, + { + "day": "05-06", + "temperature": [ + 5, + 16 + ] + }, + { + "day": "05-07", + "temperature": [ + 3, + 12 + ] + }, + { + "day": "05-08", + "temperature": [ + 0, + 8 + ] + }, + { + "day": "05-09", + "temperature": [ + -3, + 5 + ] + } +] + + + +composed_chart_example = """rx.recharts.composed_chart( + rx.recharts.area( + data_key="uv", + stroke="#8884d8", + fill="#8884d8" + ), + rx.recharts.bar( + data_key="amt", + bar_size=20, + fill="#413ea0" + ), + rx.recharts.line( + data_key="pv", + type_="monotone", + stroke="#ff7300" + ), + rx.recharts.x_axis(data_key="name"), + rx.recharts.y_axis(), + rx.recharts.cartesian_grid(stroke_dasharray="3 3"), + rx.recharts.graphing_tooltip(), + data=data)""" +``` + +Tooltips are the little boxes that pop up when you hover over something. Tooltips are always attached to something, like a dot on a scatter chart, or a bar on a bar chart. + +```python eval +docgraphing( + composed_chart_example, + comp = eval(composed_chart_example), + data = "data=" + str(data) +) +``` diff --git a/docs/library/graphing/treemap.md b/docs/library/graphing/treemap.md new file mode 100644 index 000000000..478208e5b --- /dev/null +++ b/docs/library/graphing/treemap.md @@ -0,0 +1,369 @@ +--- +components: + - rx.recharts.Treemap +--- + +# Treemap + +```python exec +import reflex as rx +from pcweb.templates.docpage import docgraphing + +data = [ + { + "name": "axis", + "children": [ + { + "name": "Axis", + "size": 24593 + }, + { + "name": "Axes", + "size": 1302 + }, + { + "name": "AxisGridLine", + "size": 652 + }, + { + "name": "AxisLabel", + "size": 636 + }, + { + "name": "CartesianAxes", + "size": 6703 + } + ] + }, + { + "name": "controls", + "children": [ + { + "name": "TooltipControl", + "size": 8435 + }, + { + "name": "SelectionControl", + "size": 7862 + }, + { + "name": "PanZoomControl", + "size": 5222 + }, + { + "name": "HoverControl", + "size": 4896 + }, + { + "name": "ControlList", + "size": 4665 + }, + { + "name": "ClickControl", + "size": 3824 + }, + { + "name": "ExpandControl", + "size": 2832 + }, + { + "name": "DragControl", + "size": 2649 + }, + { + "name": "AnchorControl", + "size": 2138 + }, + { + "name": "Control", + "size": 1353 + }, + { + "name": "IControl", + "size": 763 + } + ] + }, + { + "name": "data", + "children": [ + { + "name": "Data", + "size": 20544 + }, + { + "name": "NodeSprite", + "size": 19382 + }, + { + "name": "DataList", + "size": 19788 + }, + { + "name": "DataSprite", + "size": 10349 + }, + { + "name": "EdgeSprite", + "size": 3301 + }, + { + "name": "render", + "children": [ + { + "name": "EdgeRenderer", + "size": 5569 + }, + { + "name": "ShapeRenderer", + "size": 2247 + }, + { + "name": "ArrowType", + "size": 698 + }, + { + "name": "IRenderer", + "size": 353 + } + ] + }, + { + "name": "ScaleBinding", + "size": 11275 + }, + { + "name": "TreeBuilder", + "size": 9930 + }, + { + "name": "Tree", + "size": 7147 + } + ] + }, + { + "name": "events", + "children": [ + { + "name": "DataEvent", + "size": 7313 + }, + { + "name": "SelectionEvent", + "size": 6880 + }, + { + "name": "TooltipEvent", + "size": 3701 + }, + { + "name": "VisualizationEvent", + "size": 2117 + } + ] + }, + { + "name": "legend", + "children": [ + { + "name": "Legend", + "size": 20859 + }, + { + "name": "LegendRange", + "size": 10530 + }, + { + "name": "LegendItem", + "size": 4614 + } + ] + }, + { + "name": "operator", + "children": [ + { + "name": "distortion", + "children": [ + { + "name": "Distortion", + "size": 6314 + }, + { + "name": "BifocalDistortion", + "size": 4461 + }, + { + "name": "FisheyeDistortion", + "size": 3444 + } + ] + }, + { + "name": "encoder", + "children": [ + { + "name": "PropertyEncoder", + "size": 4138 + }, + { + "name": "Encoder", + "size": 4060 + }, + { + "name": "ColorEncoder", + "size": 3179 + }, + { + "name": "SizeEncoder", + "size": 1830 + }, + { + "name": "ShapeEncoder", + "size": 1690 + } + ] + }, + { + "name": "filter", + "children": [ + { + "name": "FisheyeTreeFilter", + "size": 5219 + }, + { + "name": "VisibilityFilter", + "size": 3509 + }, + { + "name": "GraphDistanceFilter", + "size": 3165 + } + ] + }, + { + "name": "IOperator", + "size": 1286 + }, + { + "name": "label", + "children": [ + { + "name": "Labeler", + "size": 9956 + }, + { + "name": "RadialLabeler", + "size": 3899 + }, + { + "name": "StackedAreaLabeler", + "size": 3202 + } + ] + }, + { + "name": "layout", + "children": [ + { + "name": "RadialTreeLayout", + "size": 12348 + }, + { + "name": "NodeLinkTreeLayout", + "size": 12870 + }, + { + "name": "CirclePackingLayout", + "size": 12003 + }, + { + "name": "CircleLayout", + "size": 9317 + }, + { + "name": "TreeMapLayout", + "size": 9191 + }, + { + "name": "StackedAreaLayout", + "size": 9121 + }, + { + "name": "Layout", + "size": 7881 + }, + { + "name": "AxisLayout", + "size": 6725 + }, + { + "name": "IcicleTreeLayout", + "size": 4864 + }, + { + "name": "DendrogramLayout", + "size": 4853 + }, + { + "name": "ForceDirectedLayout", + "size": 8411 + }, + { + "name": "BundledEdgeRouter", + "size": 3727 + }, + { + "name": "IndentedTreeLayout", + "size": 3174 + }, + { + "name": "PieLayout", + "size": 2728 + }, + { + "name": "RandomLayout", + "size": 870 + } + ] + }, + { + "name": "OperatorList", + "size": 5248 + }, + { + "name": "OperatorSequence", + "size": 4190 + }, + { + "name": "OperatorSwitch", + "size": 2581 + }, + { + "name": "Operator", + "size": 2490 + }, + { + "name": "SortOperator", + "size": 2023 + } + ] + } +] + + +treemap_example = """rx.recharts.treemap( + rx.recharts.graphing_tooltip(), + data=data, + data_key="size", + stroke="#fff", + fill="#8884d8")""" + +``` + +Treemaps display hierarchical (tree-structured) data as a set of nested rectangles. Each branch of the tree is given a rectangle, which is then tiled with smaller rectangles representing sub-branches. A leaf node's rectangle has an area proportional to a specified dimension of the data. + +```python eval +docgraphing(treemap_example, comp=eval(treemap_example), data = "data=" + str(data)) +``` diff --git a/docs/library/layout/aspectratio.md b/docs/library/layout/aspectratio.md new file mode 100644 index 000000000..ca731b8a1 --- /dev/null +++ b/docs/library/layout/aspectratio.md @@ -0,0 +1,105 @@ +--- +components: + - rx.radix.aspect_ratio +--- + +```python exec +import reflex as rx +``` + +# Aspect Ratio + +Displays content with a desired ratio. + +## Basic Example + +Setting the `ratio` prop will adjust the width or height +of the content such that the `width` divided by the `height` equals the `ratio`. +For responsive scaling, set the `width` or `height` of the content to `"100%"`. + +```python demo +rx.grid( + rx.aspect_ratio( + rx.box( + "Widescreen 16:9", + background_color="papayawhip", + width="100%", + height="100%", + ), + ratio=16 / 9, + ), + rx.aspect_ratio( + rx.box( + "Letterbox 4:3", + background_color="orange", + width="100%", + height="100%", + ), + ratio=4 / 3, + ), + rx.aspect_ratio( + rx.box( + "Square 1:1", + background_color="green", + width="100%", + height="100%", + ), + ratio=1, + ), + rx.aspect_ratio( + rx.box( + "Portrait 5:7", + background_color="lime", + width="100%", + height="100%", + ), + ratio=5 / 7, + ), + spacing="2", + width="25%", +) +``` + +```python eval +rx.chakra.alert( + rx.chakra.alert_icon(), + rx.box( + rx.chakra.alert_title( + "Never set ", + rx.code("height"), + " or ", + rx.code("width"), + " directly on an ", + rx.code("aspect_ratio"), + " component or its contents.", + ), + rx.chakra.alert_description( + "Instead, wrap the ", + rx.code("aspect_ratio"), + " in a ", + rx.code("box"), + " that constrains either the width or the height, then set the content width and height to ", + rx.code('"100%"'), + ".", + ), + ), + status="warning", +) +``` + +```python demo +rx.flex( + *[ + rx.box( + rx.aspect_ratio( + rx.image(src="/logo.jpg", width="100%", height="100%"), + ratio=ratio, + ), + width="20%", + ) + for ratio in [16 / 9, 3 / 2, 2 / 3, 1] + ], + justify="between", + width="100%", +) +``` diff --git a/docs/library/layout/box.md b/docs/library/layout/box.md new file mode 100644 index 000000000..215bb1188 --- /dev/null +++ b/docs/library/layout/box.md @@ -0,0 +1,46 @@ +--- +components: + - rx.radix.box +--- + +```python exec +import reflex as rx +``` + +# Box + +Box is a generic container component that can be used to group other components. + +By default, the Box component is based on the `div` and rendered as a block element. It's primary use is for applying styles. + +## Basic Example + +```python demo +rx.box( + rx.box("CSS color", background_color="yellow", border_radius="2px", width="20%", margin="4px", padding="4px"), + rx.box("CSS color", background_color="orange", border_radius="5px", width="40%", margin="8px", padding="8px"), + rx.box("Radix Color", background_color="var(--tomato-3)", border_radius="5px", width="60%", margin="12px", padding="12px"), + rx.box("Radix Color", background_color="var(--plum-3)", border_radius="10px", width="80%", margin="16px", padding="16px"), + rx.box("Radix Theme Color", background_color="var(--accent-2)", radius="full", width="100%", margin="24px", padding="25px"), + flex_grow="1", + text_align="center", +) +``` + +## Background + +To set a background [image](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_images) or +[gradient](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_images/Using_CSS_gradients), +use the [`background` CSS prop](https://developer.mozilla.org/en-US/docs/Web/CSS/background). + +```python demo +rx.flex( + rx.box(background="linear-gradient(45deg, var(--tomato-9), var(--plum-9))", width="20%", height="100%"), + rx.box(background="linear-gradient(red, yellow, blue, orange)", width="20%", height="100%"), + rx.box(background="radial-gradient(at 0% 30%, red 10px, yellow 30%, #1e90ff 50%)", width="20%", height="100%"), + rx.box(background="center/cover url('/reflex_banner.png')", width="20%", height="100%"), + spacing="2", + width="100%", + height="10vh", +) +``` diff --git a/docs/library/layout/card.md b/docs/library/layout/card.md new file mode 100644 index 000000000..75259e91d --- /dev/null +++ b/docs/library/layout/card.md @@ -0,0 +1,58 @@ +--- +components: + - rx.radix.card + +Card: | + lambda **props: rx.radix.themes.card("Basic Card ", **props) +--- + +```python exec +import reflex as rx +``` + +# Card + +A Card component is used for grouping related components. It is similar to the Box, except it has a +border, uses the theme colors and border radius, and provides a `size` prop to control spacing +and margin according to the Radix `"1"` - `"5"` scale. + +The Card requires less styling than a Box to achieve consistent visual results when used with +themes. + +## Basic Example + +```python demo +rx.flex( + rx.card("Card 1", size="1"), + rx.card("Card 2", size="2"), + rx.card("Card 3", size="3"), + rx.card("Card 4", size="4"), + rx.card("Card 5", size="5"), + spacing="2", + align_items="flex-start", + flex_wrap="wrap", +) +``` + +## Rendering as a Different Element + +The `as_child` prop may be used to render the Card as a different element. Link and Button are +commonly used to make a Card clickable. + +```python demo +rx.card( + rx.link( + rx.flex( + rx.avatar(src="/reflex_banner.png"), + rx.box( + rx.heading("Quick Start"), + rx.text("Get started with Reflex in 5 minutes."), + ), + spacing="2", + ), + ), + as_child=True, +) +``` + +## Using Inset Content diff --git a/docs/library/layout/center.md b/docs/library/layout/center.md new file mode 100644 index 000000000..dabd24189 --- /dev/null +++ b/docs/library/layout/center.md @@ -0,0 +1,21 @@ +--- +components: + - rx.radix.center +--- + +```python exec +import reflex as rx +``` + +# Center + +`Center` is a component that centers its children within itself. It is based on the `flex` component and therefore inherits all of its props. + +```python demo +rx.center( + rx.text("Hello World!"), + border_radius="15px", + border_width="thick", + width="50%", +) +``` diff --git a/docs/library/layout/cond.md b/docs/library/layout/cond.md new file mode 100644 index 000000000..2b8d2fe37 --- /dev/null +++ b/docs/library/layout/cond.md @@ -0,0 +1,80 @@ +--- +components: + - rx.components.core.Cond +--- + +```python exec +import reflex as rx +``` + +# Cond + +This component is used to conditionally render components. + +The cond component takes a condition and two components. +If the condition is `True`, the first component is rendered, otherwise the second component is rendered. + +```python demo exec +class CondState(rx.State): + show: bool = True + + def change(self): + self.show = not (self.show) + + +def cond_example(): + return rx.vstack( + rx.button("Toggle", on_click=CondState.change), + rx.cond(CondState.show, rx.text("Text 1", color="blue"), rx.text("Text 2", color="red")), + ) +``` + +The second component is optional and can be omitted. +If it is omitted, nothing is rendered if the condition is `False`. + +## Negation + +You can use the logical operator `~` to negate a condition. + +```python +rx.vstack( + rx.button("Toggle", on_click=CondState.change), + rx.cond(CondState.show, rx.text("Text 1", color="blue"), rx.text("Text 2", color="red")), + rx.cond(~CondState.show, rx.text("Text 1", color="blue"), rx.text("Text 2", color="red")), +) +``` + +## Multiple Conditions + +You can use the logical operators `&` and `|` to combine multiple conditions. + +```python demo exec +class MultiCondState(rx.State): + cond1: bool = True + cond2: bool = False + cond3: bool = True + + def change(self): + self.cond1 = not (self.cond1) + + +def multi_cond_example(): + return rx.vstack( + rx.button("Toggle", on_click=MultiCondState.change), + rx.text( + rx.cond(MultiCondState.cond1, "True", "False"), + " & True => ", + rx.cond(MultiCondState.cond1 & MultiCondState.cond3, "True", "False"), + ), + rx.text( + rx.cond(MultiCondState.cond1, "True", "False"), + " & False => ", + rx.cond(MultiCondState.cond1 & MultiCondState.cond2, "True", "False"), + ), + rx.text( + rx.cond(MultiCondState.cond1, "True", "False"), + " | False => ", + rx.cond(MultiCondState.cond1 | MultiCondState.cond2, "True", "False"), + ), + ) +``` diff --git a/docs/library/layout/container.md b/docs/library/layout/container.md new file mode 100644 index 000000000..fdeed5f43 --- /dev/null +++ b/docs/library/layout/container.md @@ -0,0 +1,40 @@ +--- +components: + - rx.radix.container +--- + +```python exec +import reflex as rx +``` + +# Container + +Constrains the maximum width of page content, while keeping flexible margins +for responsive layouts. + +A Container is generally used to wrap the main content for a page. + +## Basic Example + +```python demo +rx.box( + rx.container( + rx.card("This content is constrained to a max width of 448px.", width="100%"), + size="1", + ), + rx.container( + rx.card("This content is constrained to a max width of 688px.", width="100%"), + size="2", + ), + rx.container( + rx.card("This content is constrained to a max width of 880px.", width="100%"), + size="3", + ), + rx.container( + rx.card("This content is constrained to a max width of 1136px.", width="100%"), + size="4", + ), + background_color="var(--gray-3)", + width="100%", +) +``` diff --git a/docs/library/layout/flex.md b/docs/library/layout/flex.md new file mode 100644 index 000000000..23861ff6b --- /dev/null +++ b/docs/library/layout/flex.md @@ -0,0 +1,188 @@ +--- +components: + - rx.radix.flex +--- + +```python exec +import reflex as rx +``` + +# Flex + +The Flex component is used to make [flexbox layouts](https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox). +It makes it simple to arrange child components in horizontal or vertical directions, apply wrapping, +justify and align content, and automatically size components based on available space, making it +ideal for building responsive layouts. + +By default, children are arranged horizontally (`direction="row"`) without wrapping. + +## Basic Example + +```python demo +rx.flex( + rx.card("Card 1"), + rx.card("Card 2"), + rx.card("Card 3"), + rx.card("Card 4"), + rx.card("Card 5"), + spacing="2", + width="100%", +) +``` + +## Wrapping + +With `flex_wrap="wrap"`, the children will wrap to the next line instead of being resized. + +```python demo +rx.flex( + rx.foreach( + rx.Var.range(10), + lambda i: rx.card(f"Card {i + 1}", width="16%"), + ), + spacing="2", + flex_wrap="wrap", + width="100%", +) +``` + +## Direction + +With `direction="column"`, the children will be arranged vertically. + +```python demo +rx.flex( + rx.card("Card 1"), + rx.card("Card 2"), + rx.card("Card 3"), + rx.card("Card 4"), + spacing="2", + direction="column", +) +``` + +## Alignment + +Two props control how children are aligned within the Flex component: + +* `align` controls how children are aligned along the cross axis (vertical for `row` and horizontal for `column`). +* `justify` controls how children are aligned along the main axis (horizontal for `row` and vertical for `column`). + +The following example visually demonstrates the effect of these props with different `wrap` and `direction` values. + +```python demo exec +class FlexPlaygroundState(rx.State): + align: str = "stretch" + justify: str = "start" + direction: str = "row" + wrap: str = "nowrap" + + +def select(label, items, value, on_change): + return rx.flex( + rx.text(label), + rx.select.root( + rx.select.trigger(), + rx.select.content( + *[ + rx.select.item(item, value=item) + for item in items + ] + ), + value=value, + on_change=on_change, + ), + align="center", + justify="center", + direction="column", + ) + + +def selectors(): + return rx.flex( + select("Wrap", ["nowrap", "wrap", "wrap-reverse"], FlexPlaygroundState.wrap, FlexPlaygroundState.set_wrap), + select("Direction", ["row", "column", "row-reverse", "column-reverse"], FlexPlaygroundState.direction, FlexPlaygroundState.set_direction), + select("Align", ["start", "center", "end", "baseline", "stretch"], FlexPlaygroundState.align, FlexPlaygroundState.set_align), + select("Justify", ["start", "center", "end", "between"], FlexPlaygroundState.justify, FlexPlaygroundState.set_justify), + width="100%", + spacing="2", + justify="between", + ) + + +def example1(): + return rx.box( + selectors(), + rx.flex( + rx.foreach( + rx.Var.range(10), + lambda i: rx.card(f"Card {i + 1}", width="16%"), + ), + spacing="2", + direction=FlexPlaygroundState.direction, + align=FlexPlaygroundState.align, + justify=FlexPlaygroundState.justify, + wrap=FlexPlaygroundState.wrap, + width="100%", + height="20vh", + margin_top="16px", + ), + width="100%", + ) +``` + +## Size Hinting + +When a child component is included in a flex container, +the `flex_grow` (default `"0"`) and `flex_shrink` (default `"1"`) props control +how the box is sized relative to other components in the same container. + +The resizing always applies to the main axis of the flex container. If the direction is +`row`, then the sizing applies to the `width`. If the direction is `column`, then the sizing +applies to the `height`. To set the optimal size along the main axis, the `flex_basis` prop +is used and may be either a percentage or CSS size units. When unspecified, the +corresponding `width` or `height` value is used if set, otherwise the content size is used. + +When `flex_grow="0"`, the box will not grow beyond the `flex_basis`. + +When `flex_shrink="0"`, the box will not shrink to less than the `flex_basis`. + +These props are used when creating flexible responsive layouts. + +Move the slider below and see how adjusting the width of the flex container +affects the computed sizes of the flex items based on the props that are set. + +```python demo exec +class FlexGrowShrinkState(rx.State): + width_pct: list[int] = [100] + + +def border_box(*children, **props): + return rx.box( + *children, + border="1px solid var(--gray-10)", + border_radius="2px", + **props, + ) + + +def example2(): + return rx.box( + rx.flex( + border_box("flex_shrink=0", flex_shrink="0", width="100px"), + border_box("flex_shrink=1", flex_shrink="1", width="200px"), + border_box("flex_grow=0", flex_grow="0"), + border_box("flex_grow=1", flex_grow="1"), + width=f"{FlexGrowShrinkState.width_pct}%", + margin_bottom="16px", + spacing="2", + ), + rx.slider( + min=0, + max=100, + value=FlexGrowShrinkState.width_pct, + on_change=FlexGrowShrinkState.set_width_pct, + ), + width="100%", + ) +``` diff --git a/docs/library/layout/foreach.md b/docs/library/layout/foreach.md new file mode 100644 index 000000000..7af54ebe4 --- /dev/null +++ b/docs/library/layout/foreach.md @@ -0,0 +1,198 @@ +--- +components: + - rx.foreach +--- + +```python exec +import reflex as rx +``` + +# Foreach + +The `rx.foreach` component takes an iterable(list, tuple or dict) and a function that renders each item in the list. +This is useful for dynamically rendering a list of items defined in a state. + +```md alert warning +# `rx.foreach` is specialized for usecases where the iterable is defined in a state var. +For an iterable where the content doesn't change at runtime, i.e a constant, using a list/dict comprehension instead of `rx.foreach` is preferred. +``` + +```python demo exec +from typing import List +class ForeachState(rx.State): + color: List[str] = ["red", "green", "blue", "yellow", "orange", "purple"] + +def colored_box(color: str): + return rx.box( + rx.text(color), + bg=color + ) + +def foreach_example(): + return rx.chakra.responsive_grid( + rx.foreach( + ForeachState.color, + colored_box + ), + columns=[2, 4, 6], + ) +``` + +The function can also take an index as a second argument. + +```python demo exec +def colored_box_index(color: str, index: int): + return rx.box( + rx.text(index), + bg=color + ) + +def foreach_example_index(): + return rx.chakra.responsive_grid( + rx.foreach( + ForeachState.color, + lambda color, index: colored_box_index(color, index) + ), + columns=[2, 4, 6], + ) +``` + +Nested foreach components can be used to render nested lists. + +When indexing into a nested list, it's important to declare the list's type as Reflex requires it for type checking. +This ensures that any potential frontend JS errors are caught before the user can encounter them. + +```python demo exec +from typing import List + +class NestedForeachState(rx.State): + numbers: List[List[str]] = [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]] + +def display_row(row): + return rx.hstack( + rx.foreach( + row, + lambda item: rx.box( + item, + border="1px solid black", + padding="0.5em", + ) + ), + ) + +def nested_foreach_example(): + return rx.vstack( + rx.foreach( + NestedForeachState.numbers, + display_row + ) + ) +``` + +Below is a more complex example of foreach within a todo list. + +```python demo exec +from typing import List +class ListState(rx.State): + items: List[str] = ["Write Code", "Sleep", "Have Fun"] + new_item: str + + def add_item(self): + self.items += [self.new_item] + + def finish_item(self, item: str): + self.items = [i for i in self.items if i != item] + +def get_item(item): + return rx.chakra.list_item( + rx.hstack( + rx.button( + on_click=lambda: ListState.finish_item(item), + height="1.5em", + background_color="white", + border="1px solid blue", + ), + rx.text(item, font_size="1.25em"), + ), + ) + +def todo_example(): + return rx.vstack( + rx.heading("Todos"), + rx.chakra.input(on_blur=ListState.set_new_item, placeholder="Add a todo...", bg = "white"), + rx.button("Add", on_click=ListState.add_item, bg = "white"), + rx.chakra.divider(), + rx.chakra.ordered_list( + rx.foreach( + ListState.items, + get_item, + ), + ), + bg = "#ededed", + padding = "1em", + border_radius = "0.5em", + shadow = "lg" + ) +``` + +## Dictionaries + +Items in a dictionary can be accessed as list of key-value pairs. +Using the color example, we can slightly modify the code to use dicts as shown below. + +```python demo exec +from typing import List +class SimpleDictForeachState(rx.State): + color_chart: dict[int, str] = { + 1 : "blue", + 2: "red", + 3: "green" + } + +def display_color(color: List): + # color is presented as a list key-value pair([1, "blue"],[2, "red"], [3, "green"]) + return rx.box(rx.text(color[0]), bg=color[1]) + + +def foreach_dict_example(): + return rx.chakra.responsive_grid( + rx.foreach( + SimpleDictForeachState.color_chart, + display_color + ), + columns = [2, 4, 6] + ) +``` + +Now let's show a more complex example with dicts using the color example. +Assuming we want to display a dictionary of secondary colors as keys and their constituent primary colors as values, we can modify the code as below: + +```python demo exec +from typing import List, Dict +class ComplexDictForeachState(rx.State): + color_chart: Dict[str, List[str]] = { + "purple": ["red", "blue"], + "orange": ["yellow", "red"], + "green": ["blue", "yellow"] + } + +def display_colors(color: List): + return rx.vstack( + rx.text(color[0], color=color[0]), + rx.hstack( + rx.foreach( + color[1], lambda x: rx.box(rx.text(x, color="black"), bg=x) + ) + + ) + ) + +def foreach_complex_dict_example(): + return rx.chakra.responsive_grid( + rx.foreach( + ComplexDictForeachState.color_chart, + display_colors + ), + columns=[2, 4, 6] + ) +``` diff --git a/docs/library/layout/fragment.md b/docs/library/layout/fragment.md new file mode 100644 index 000000000..1d2240ed8 --- /dev/null +++ b/docs/library/layout/fragment.md @@ -0,0 +1,22 @@ +--- +components: + - rx.fragment +--- + +# Fragment + +```python exec +import reflex as rx +from pcweb import constants +``` + +A Fragment is a Component that allow you to group multiple Components without a wrapper node. + +Refer to the React docs at [React/Fragment]({constants.FRAGMENT_COMPONENT_INFO_URL}) for more information on its use-case. + +```python demo +rx.fragment( + rx.text("Component1"), + rx.text("Component2") +) +``` diff --git a/docs/library/layout/grid.md b/docs/library/layout/grid.md new file mode 100644 index 000000000..068b7aec0 --- /dev/null +++ b/docs/library/layout/grid.md @@ -0,0 +1,40 @@ +--- +components: + - rx.radix.grid +--- + +```python exec +import reflex as rx +``` + +# Grid + +Component for creating grid layouts. Either `rows` or `columns` may be specified. + +## Basic Example + +```python demo +rx.grid( + rx.foreach( + rx.Var.range(12), + lambda i: rx.card(f"Card {i + 1}", height="10vh"), + ), + columns="3", + spacing="4", + width="100%", +) +``` + +```python demo +rx.grid( + rx.foreach( + rx.Var.range(12), + lambda i: rx.card(f"Card {i + 1}", height="10vh"), + ), + rows="3", + flow="column", + justify="between", + spacing="4", + width="100%", +) +``` diff --git a/docs/library/layout/inset.md b/docs/library/layout/inset.md new file mode 100644 index 000000000..146aa2968 --- /dev/null +++ b/docs/library/layout/inset.md @@ -0,0 +1,90 @@ +--- +components: + - rx.radix.inset + +Inset: | + lambda **props: rx.radix.themes.card( + rx.radix.themes.inset( + rx.image(src="/reflex_banner.png", height="auto"), + **props, + ), + width="500px", + ) + +--- + +```python exec +import reflex as rx +``` + +# Inset + +Applies a negative margin to allow content to bleed into the surrounding container. + +## Basic Example + +Nesting an Inset component inside a Card will render the content from edge to edge of the card. + +```python demo +rx.card( + rx.inset( + rx.image(src="/reflex_banner.png", width="100%", height="auto"), + side="top", + pb="current", + ), + rx.text("Reflex is a web framework that allows developers to build their app in pure Python."), + width="25vw", +) +``` + +## Other Directions + +The `side` prop controls which side the negative margin is applied to. When using a specific side, +it is helpful to set the padding for the opposite side to `current` to retain the same padding the +content would have had if it went to the edge of the parent component. + +```python demo +rx.card( + rx.text("The inset below uses a bottom side."), + rx.inset( + rx.image(src="/reflex_banner.png", width="100%", height="auto"), + side="bottom", + pt="current", + ), + width="25vw", +) +``` + +```python demo +rx.card( + rx.flex( + rx.text("This inset uses a right side, which requires a flex with direction row."), + rx.inset( + rx.box(background="center/cover url('/reflex_banner.png')", height="100%"), + width="100%", + side="right", + pl="current", + ), + direction="row", + width="100%", + ), + width="25vw", +) +``` + +```python demo +rx.card( + rx.flex( + rx.inset( + rx.box(background="center/cover url('/reflex_banner.png')", height="100%"), + width="100%", + side="left", + pr="current", + ), + rx.text("This inset uses a left side, which also requires a flex with direction row."), + direction="row", + width="100%", + ), + width="25vw", +) +``` diff --git a/docs/library/layout/match.md b/docs/library/layout/match.md new file mode 100644 index 000000000..aee2fe798 --- /dev/null +++ b/docs/library/layout/match.md @@ -0,0 +1,289 @@ +--- +components: + - rx.match +--- + +```python exec +import reflex as rx +``` + +# Match + +The `rx.match` feature in Reflex serves as a powerful alternative to `rx.cond` for handling conditional statements. +While `rx.cond` excels at conditionally rendering two components based on a single condition, +`rx.match` extends this functionality by allowing developers to handle multiple conditions and their associated components. +This feature is especially valuable when dealing with intricate conditional logic or nested scenarios, +where the limitations of `rx.cond` might lead to less readable and more complex code. + +With `rx.match`, developers can not only handle multiple conditions but also perform structural pattern matching, +making it a versatile tool for managing various scenarios in Reflex applications. + +## Basic Usage + +The `rx.match` function provides a clear and expressive syntax for handling multiple +conditions and their corresponding components: + +```python +rx.match( + condition, + (case_1, component_1), + (case_2, component_2), + ... + default_component, +) + +``` + +- `condition`: The value to match against. +- `(case_i, component_i)`: A Tuple of matching cases and their corresponding return components. +- `default_component`: A special case for the default component when the condition isnt matched by any of the match cases. + +Example + +```python demo exec +from typing import List + +import reflex as rx + + +class MatchState(rx.State): + cat_breed: str = "" + animal_options: List[str] = ["persian", "siamese", "maine coon", "ragdoll", "pug", "corgi"] + + +def match_demo(): + return rx.flex( + rx.match( + MatchState.cat_breed, + ("persian", rx.text("Persian cat selected.")), + ("siamese", rx.text("Siamese cat selected.")), + ("maine coon", rx.text("Maine Coon cat selected.")), + ("ragdoll", rx.text("Ragdoll cat selected.")), + rx.text("Unknown cat breed selected.") + ), + rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.foreach(MatchState.animal_options, lambda x: rx.select.item(x, value=x)) + ), + ), + value=MatchState.cat_breed, + on_change=MatchState.set_cat_breed, + + ), + direction= "column", + gap= "2" + ) +``` + +## Default Case + +The default case in `rx.match` serves as a universal handler for scenarios where none of +the specified match cases aligns with the given match condition. Here are key considerations +when working with the default case: + +- **Placement in the Match Function**: The default case must be the last non-tuple argument in the `rx.match` component. +All match cases should be enclosed in tuples; any non-tuple value is automatically treated as the default case. For example: + + ```python +rx.match( + MatchState.cat_breed, + ("persian", rx.text("persian cat selected")), + rx.text("Unknown cat breed selected."), + ("siamese", rx.text("siamese cat selected")), + ) +``` + +The above code snippet will result in an error due to the misplaced default case. + +- **Single Default Case**: Only one default case is allowed in the `rx.match` component. +Attempting to specify multiple default cases will lead to an error. For instance: + + ```python +rx.match( + MatchState.cat_breed, + ("persian", rx.text("persian cat selected")), + ("siamese", rx.text("siamese cat selected")), + rx.text("Unknown cat breed selected."), + rx.text("Another unknown cat breed selected.") + ) +``` + +- **Optional Default Case for Component Return Values**: If the match cases in a `rx.match` component +return components, the default case can be optional. In this scenario, if a default case is +not provided, `rx.fragment` will be implicitly assigned as the default. For example: + + ```python +rx.match( + MatchState.cat_breed, + ("persian", rx.text("persian cat selected")), + ("siamese", rx.text("siamese cat selected")), + ) +``` + +In this case, `rx.fragment` is the default case. However, not providing a default case for non-component +return values will result in an error: + + ```python +rx.match( + MatchState.cat_breed, + ("persian", "persian cat selected"), + ("siamese", "siamese cat selected"), + ) +``` + +The above code snippet will result in an error as a default case must be explicitly +provided in this scenario. + +## Handling Multiple Conditions + +`rx.match` excels in efficiently managing multiple conditions and their corresponding components, +providing a cleaner and more readable alternative compared to nested `rx.cond` structures. + +Consider the following example: + +```python demo exec +from typing import List + +import reflex as rx + + +class MultiMatchState(rx.State): + animal_breed: str = "" + animal_options: List[str] = ["persian", "siamese", "maine coon", "pug", "corgi", "mustang", "rahvan", "football", "golf"] + +def multi_match_demo(): + return rx.flex( + rx.match( + MultiMatchState.animal_breed, + ("persian", "siamese", "maine coon", rx.text("Breeds of cats.")), + ("pug", "corgi", rx.text("Breeds of dogs.")), + ("mustang", "rahvan", rx.text("Breeds of horses.")), + rx.text("Unknown animal breed") + ), + rx.select.root( + rx.select.trigger(), + rx.select.content( + rx.select.group( + rx.foreach(MultiMatchState.animal_options, lambda x: rx.select.item(x, value=x)) + ), + ), + value=MultiMatchState.animal_breed, + on_change=MultiMatchState.set_animal_breed, + + ), + direction= "column", + gap= "2" + ) +``` + +In a match case tuple, you can specify multiple conditions. The last value of the match case +tuple is automatically considered as the return value. It's important to note that a match case +tuple should contain a minimum of two elements: a match case and a return value. +The following code snippet will result in an error: + +```python +rx.match( + MatchState.cat_breed, + ("persian",), + ("maine coon", rx.text("Maine Coon cat selected")), + ) +``` + +## Usage as Props + +Similar to `rx.cond`, `rx.match` can be used as prop values, allowing dynamic behavior for UI components: + +```python demo exec +import reflex as rx + + +class MatchPropState(rx.State): + value: int = 0 + + def incr(self): + self.value += 1 + + def decr(self): + self.value -= 1 + + +def match_prop_demo_(): + return rx.flex( + rx.button("decrement", on_click=MatchPropState.decr, background_color="red"), + rx.badge( + MatchPropState.value, + color_scheme= rx.match( + MatchPropState.value, + (1, "red"), + (2, "blue"), + (6, "purple"), + (10, "orange"), + "green" + ), + size="2", + ), + rx.button("increment", on_click=MatchPropState.incr), + align_items="center", + direction= "row", + gap= "3" + ) +``` + +In the example above, the background color property of the box component containing `State.value` changes to red when +`state.value` is 1, blue when its 5, green when its 5, orange when its 15 and black for any other value. + +The example below also shows handling multiple conditions with the match component as props. + +```python demo exec +import reflex as rx + + +class MatchMultiPropState(rx.State): + value: int = 0 + + def incr(self): + self.value += 1 + + def decr(self): + self.value -= 1 + + +def match_multi_prop_demo_(): + return rx.flex( + rx.button("decrement", on_click=MatchMultiPropState.decr, background_color="red"), + rx.badge( + MatchMultiPropState.value, + color_scheme= rx.match( + MatchMultiPropState.value, + (1, 3, 9, "red"), + (2, 4, 5, "blue"), + (6, 8, 12, "purple"), + (10, 15, 20, 25, "orange"), + "green" + ), + size="2", + ), + rx.button("increment", on_click=MatchMultiPropState.incr), + align_items="center", + direction= "row", + gap= "3" + ) +``` + +```md alert warning +# Usage with Structural Pattern Matching + +The `rx.match` component is designed for structural pattern matching. If the value of your match condition evaluates to a boolean (True or False), it is recommended to use `rx.cond` instead. + +Consider the following example, which is more suitable for `rx.cond`:* +``` + +```python +rx.cond( + MatchPropState.value == 10, + "true value", + "false value" +) +``` diff --git a/docs/library/layout/section.md b/docs/library/layout/section.md new file mode 100644 index 000000000..e55a354d1 --- /dev/null +++ b/docs/library/layout/section.md @@ -0,0 +1,36 @@ +--- +components: + - rx.radix.section +--- + +```python exec +import reflex as rx +``` + +# Section + +Denotes a section of page content, providing vertical padding by default. + +Primarily this is a semantic component that is used to group related textual content. + +## Basic Example + +```python demo +rx.box( + rx.section( + rx.heading("First"), + rx.text("This is the first content section"), + padding_left="12px", + padding_right="12px", + background_color="var(--gray-2)", + ), + rx.section( + rx.heading("Second"), + rx.text("This is the second content section"), + padding_left="12px", + padding_right="12px", + background_color="var(--gray-2)", + ), + width="100%", +) +``` diff --git a/docs/library/layout/separator.md b/docs/library/layout/separator.md new file mode 100644 index 000000000..5fcedcd26 --- /dev/null +++ b/docs/library/layout/separator.md @@ -0,0 +1,59 @@ +--- +components: + - rx.radix.separator +Separator: | + lambda **props: rx.radix.themes.separator(**props) + +--- + +```python exec +import reflex as rx +``` + +# Separator + +Visually or semantically separates content. + +## Basic Example + +```python demo +rx.flex( + rx.card("Section 1"), + rx.divider(), + rx.card("Section 2"), + spacing="4", + direction="column", + align="center", +) +``` + +## Size + +The `size` prop controls how long the separator is. Using `size="4"` will make +the separator fill the parent container. Setting CSS `width` or `height` prop to `"100%"` +can also achieve this effect, but `size` works the same regardless of the orientation. + +```python demo +rx.flex( + rx.card("Section 1"), + rx.divider(size="4"), + rx.card("Section 2"), + spacing="4", + direction="column", +) +``` + +## Orientation + +Setting the orientation prop to `vertical` will make the separator appear vertically. + +```python demo +rx.flex( + rx.card("Section 1"), + rx.divider(orientation="vertical", size="4"), + rx.card("Section 2"), + spacing="4", + width="100%", + height="10vh", +) +``` diff --git a/docs/library/layout/spacer.md b/docs/library/layout/spacer.md new file mode 100644 index 000000000..02959ebe5 --- /dev/null +++ b/docs/library/layout/spacer.md @@ -0,0 +1,25 @@ +--- +components: + - rx.radix.spacer +--- + +```python exec +import reflex as rx +``` + +# Spacer + +Creates an adjustable, empty space that can be used to tune the spacing between child elements within `flex`. + +```python demo +rx.flex( + rx.center(rx.text("Example"), bg="lightblue"), + rx.spacer(), + rx.center(rx.text("Example"), bg="lightgreen"), + rx.spacer(), + rx.center(rx.text("Example"), bg="salmon"), + width="100%", +) +``` + +As `stack`, `vstack` and `hstack` are all built from `flex`, it is possible to also use `spacer` inside of these components. diff --git a/docs/library/layout/stack.md b/docs/library/layout/stack.md new file mode 100644 index 000000000..ebdb6a110 --- /dev/null +++ b/docs/library/layout/stack.md @@ -0,0 +1,165 @@ +--- +components: + - rx.radix.stack + - rx.radix.hstack + - rx.radix.vstack +--- + +```python exec +import reflex as rx +``` + +# Stack + +`Stack` is a layout component used to group elements together and apply a space between them. + +`vstack` is used to stack elements in the vertical direction. + +`hstack` is used to stack elements in the horizontal direction. + +`stack` is used to stack elements in the vertical or horizontal direction. + +These components are based on the `flex` component and therefore inherit all of its props. + +The `stack` component can be used with the `flex_direction` prop to set to either `row` or `column` to set the direction. + +```python demo +rx.flex( + rx.stack( + rx.box( + "Example", + bg="orange", + border_radius="3px", + width="20%", + ), + rx.box( + "Example", + bg="lightblue", + border_radius="3px", + width="30%", + ), + rx.box( + "Example", + bg="lightgreen", + border_radius="3px", + width="50%", + ), + flex_direction="row", + width="100%", + ), + rx.stack( + rx.box( + "Example", + bg="orange", + border_radius="3px", + width="20%", + ), + rx.box( + "Example", + bg="lightblue", + border_radius="3px", + width="30%", + ), + rx.box( + "Example", + bg="lightgreen", + border_radius="3px", + width="50%", + ), + flex_direction="column", + width="100%", + ), + width="100%", +) +``` + +## Hstack + +```python demo +rx.hstack( + rx.box( + "Example", bg="red", border_radius="3px", width="10%" + ), + rx.box( + "Example", + bg="orange", + border_radius="3px", + width="10%", + ), + rx.box( + "Example", + bg="yellow", + border_radius="3px", + width="10%", + ), + rx.box( + "Example", + bg="lightblue", + border_radius="3px", + width="10%", + ), + rx.box( + "Example", + bg="lightgreen", + border_radius="3px", + width="60%", + ), + width="100%", +) +``` + +## Vstack + +```python demo +rx.vstack( + rx.box( + "Example", bg="red", border_radius="3px", width="20%" + ), + rx.box( + "Example", + bg="orange", + border_radius="3px", + width="40%", + ), + rx.box( + "Example", + bg="yellow", + border_radius="3px", + width="60%", + ), + rx.box( + "Example", + bg="lightblue", + border_radius="3px", + width="80%", + ), + rx.box( + "Example", + bg="lightgreen", + border_radius="3px", + width="100%", + ), + width="100%", +) +``` + +## Real World Example + +```python demo +rx.hstack( + rx.box( + rx.heading("Saving Money"), + rx.text("Saving money is an art that combines discipline, strategic planning, and the wisdom to foresee future needs and emergencies. It begins with the simple act of setting aside a portion of one's income, creating a buffer that can grow over time through interest or investments.", margin_top="0.5em"), + padding="1em", + border_width="1px", + ), + rx.box( + rx.heading("Spending Money"), + rx.text("Spending money is a balancing act between fulfilling immediate desires and maintaining long-term financial health. It's about making choices, sometimes indulging in the pleasures of the moment, and at other times, prioritizing essential expenses.", margin_top="0.5em"), + padding="1em", + border_width="1px", + ), + gap="2em", +) + +``` diff --git a/docs/library/media/audio.md b/docs/library/media/audio.md new file mode 100644 index 000000000..f087ca238 --- /dev/null +++ b/docs/library/media/audio.md @@ -0,0 +1,22 @@ +--- +components: + - rx.audio +--- + +# Audio + +```python exec +import reflex as rx +``` + +The audio component can display an audio given an src path as an argument. This could either be a local path from the assets folder or an external link. + +```python demo +rx.audio( + url="https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3", + width="400px", + height="auto" +) +``` + +If we had a local file in the `assets` folder named `test.mp3` we could set `url="/test.mp3"` to view the audio file. diff --git a/docs/library/media/video.md b/docs/library/media/video.md new file mode 100644 index 000000000..5e4e1a9c6 --- /dev/null +++ b/docs/library/media/video.md @@ -0,0 +1,22 @@ +--- +components: + - rx.video +--- + +# Video + +```python exec +import reflex as rx +``` + +The video component can display a video given an src path as an argument. This could either be a local path from the assets folder or an external link. + +```python demo +rx.video( + url="https://www.youtube.com/embed/9bZkp7q19f0", + width="400px", + height="auto" +) +``` + +If we had a local file in the `assets` folder named `test.mp4` we could set `url="/test.mp3"` to view the video. diff --git a/docs/library/other/html.md b/docs/library/other/html.md new file mode 100644 index 000000000..074d740f7 --- /dev/null +++ b/docs/library/other/html.md @@ -0,0 +1,38 @@ +--- +components: + - rx.html +--- + +```python exec +import reflex as rx +from pcweb.pages.docs import styling +``` + +# HTML + +The HTML component can be used to render raw HTML code. + +Before you reach for this component, consider using Reflex's raw HTML element support instead. + +```python demo +rx.vstack( + rx.html("

Hello World

"), + rx.html("

Hello World

"), + rx.html("

Hello World

"), + rx.html("

Hello World

"), + rx.html("
Hello World
"), + rx.html("
Hello World
"), +) +``` + +```md alert +# Missing Styles? +Reflex uses Chakra-UI and tailwind for styling, both of which reset default styles for headings. +If you are using the html component and want pretty default styles, consider setting `class_name='prose'`, adding `@tailwindcss/typography` package to `frontend_packages` and enabling it via `tailwind` config in `rxconfig.py`. See the [Tailwind docs]({styling.overview.path}) for an example of adding this plugin. +``` + +In this example, we render an image. + +```python demo +rx.html("") +``` diff --git a/docs/library/other/script.md b/docs/library/other/script.md new file mode 100644 index 000000000..8d9356e7c --- /dev/null +++ b/docs/library/other/script.md @@ -0,0 +1,38 @@ +--- +components: + - rx.script +--- + +```python exec +import reflex as rx +``` + +# Script + +The Script component can be used to include inline javascript or javascript files by URL. + +It uses the [`next/script` component](https://nextjs.org/docs/app/api-reference/components/script) to inject the script and can be safely used with conditional rendering to allow script side effects to be controlled by the state. + +```python +rx.script("console.log('inline javascript')") +``` + +Complex inline scripting should be avoided. +If the code to be included is more than a couple lines, it is more maintainable to implement it in a separate javascript file in the `assets` directory and include it via the `src` prop. + +```python +rx.script(src="/my-custom.js") +``` + +This component is particularly helpful for including tracking and social scripts. +Any additional attrs needed for the script tag can be supplied via `custom_attrs` prop. + +```python +rx.script(src="//gc.zgo.at/count.js", custom_attrs=\{"data-goatcounter": "https://reflextoys.goatcounter.com/count"}) +``` + +This code renders to something like the following to enable stat counting with a third party service. + +```jsx + +``` diff --git a/docs/library/overlay/alertdialog.md b/docs/library/overlay/alertdialog.md new file mode 100644 index 000000000..ca5a6a122 --- /dev/null +++ b/docs/library/overlay/alertdialog.md @@ -0,0 +1,297 @@ +--- +components: + - rx.radix.alert_dialog.root + - rx.radix.alert_dialog.content + - rx.radix.alert_dialog.trigger + - rx.radix.alert_dialog.title + - rx.radix.alert_dialog.description + - rx.radix.alert_dialog.action + - rx.radix.alert_dialog.cancel + +only_low_level: + - True + +AlertDialogRoot: | + lambda **props: rx.radix.themes.alert_dialog.root( + rx.radix.themes.alert_dialog.trigger( + rx.radix.themes.button("Revoke access"), + ), + rx.radix.themes.alert_dialog.content( + rx.radix.themes.alert_dialog.title("Revoke access"), + rx.radix.themes.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + ), + rx.radix.themes.flex( + rx.radix.themes.alert_dialog.cancel( + rx.radix.themes.button("Cancel"), + ), + rx.radix.themes.alert_dialog.action( + rx.radix.themes.button("Revoke access"), + ), + spacing="3", + ), + ), + **props + ) + +AlertDialogContent: | + lambda **props: rx.radix.themes.alert_dialog.root( + rx.radix.themes.alert_dialog.trigger( + rx.radix.themes.button("Revoke access"), + ), + rx.radix.themes.alert_dialog.content( + rx.radix.themes.alert_dialog.title("Revoke access"), + rx.radix.themes.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + ), + rx.radix.themes.flex( + rx.radix.themes.alert_dialog.cancel( + rx.radix.themes.button("Cancel"), + ), + rx.radix.themes.alert_dialog.action( + rx.radix.themes.button("Revoke access"), + ), + spacing="3", + ), + **props + ), + ) +--- + + +```python exec +import reflex as rx +``` + +# Alert Dialog + +An alert dialog is a modal confirmation dialog that interrupts the user and expects a response. + +The `alert_dialog.root` contains all the parts of the dialog. + +The `alert_dialog.trigger` wraps the control that will open the dialog. + +The `alert_dialog.content` contains the content of the dialog. + +The `alert_dialog.title` is the title that is announced when the dialog is opened. + +The `alert_dialog.description` is an optional description that is announced when the dialog is opened. + +The `alert_dialog.action` wraps the control that will close the dialog. This should be distinguished visually from the `alert_dialog.cancel` control. + +The `alert_dialog.cancel` wraps the control that will close the dialog. This should be distinguished visually from the `alert_dialog.action` control. + +## Basic Example + +```python demo +rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Revoke access"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access"), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel"), + ), + rx.alert_dialog.action( + rx.button("Revoke access"), + ), + spacing="3", + ), + ), +) +``` + +```python demo +rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Revoke access", color_scheme="red"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access"), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + size="2", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel", variant="soft", color_scheme="gray"), + ), + rx.alert_dialog.action( + rx.button("Revoke access", color_scheme="red", variant="solid"), + ), + spacing="3", + margin_top="16px", + justify="end", + ), + style={"max_width": 450}, + ), +) +``` + +Use the `inset` component to align content flush with the sides of the dialog. + +```python demo +rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Delete Users", color_scheme="red"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Delete Users"), + rx.alert_dialog.description( + "Are you sure you want to delete these users? This action is permanent and cannot be undone.", + size="2", + ), + rx.inset( + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Rosa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + ), + ), + ), + side="x", + margin_top="24px", + margin_bottom="24px", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel", variant="soft", color_scheme="gray"), + ), + rx.alert_dialog.action( + rx.button("Delete users", color_scheme="red"), + ), + spacing="3", + justify="end", + ), + style={"max_width": 500}, + ), +) +``` + +## Events when the Alert Dialog opens or closes + +The `on_open_change` event is called when the `open` state of the dialog changes. It is used in conjunction with the `open` prop. + +```python demo exec +class AlertDialogState(rx.State): + num_opens: int = 0 + opened: bool = False + + def count_opens(self, value: bool): + self.opened = value + self.num_opens += 1 + + +def alert_dialog(): + return rx.flex( + rx.heading(f"Number of times alert dialog opened or closed: {AlertDialogState.num_opens}"), + rx.heading(f"Alert Dialog open: {AlertDialogState.opened}"), + rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Revoke access", color_scheme="red"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access"), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + size="2", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel", variant="soft", color_scheme="gray"), + ), + rx.alert_dialog.action( + rx.button("Revoke access", color_scheme="red", variant="solid"), + ), + spacing="3", + margin_top="16px", + justify="end", + ), + style={"max_width": 450}, + ), + on_open_change=AlertDialogState.count_opens, + ), + direction="column", + spacing="3", + ) +``` + +## Changing the size + +The `size` of `alert_dialog` can be changed. The `alert_dialog` on the right hand side has no `size` props set. The one on the left hand side has `size` set to `1` for all subcomponents including `alert_dialog.trigger`, `alert_dialog.content`, `alert_dialog.title`, `alert_dialog.description`, `alert_dialog.cancel` and `alert_dialog.action`. The `size` prop can take any value of `1, 2, 3, 4`. + +```python demo +rx.flex( + rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Revoke access", color_scheme="red"), + size="1", + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access", size="1",), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + size="1", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel", variant="soft", color_scheme="gray"), + size="1", + ), + rx.alert_dialog.action( + rx.button("Revoke access", color_scheme="red", variant="solid"), + size="1", + ), + spacing="3", + margin_top="16px", + justify="end", + ), + style={"max_width": 450}, + size="1", + ), + ), + rx.alert_dialog.root( + rx.alert_dialog.trigger( + rx.button("Revoke access", color_scheme="red"), + ), + rx.alert_dialog.content( + rx.alert_dialog.title("Revoke access"), + rx.alert_dialog.description( + "Are you sure? This application will no longer be accessible and any existing sessions will be expired.", + ), + rx.flex( + rx.alert_dialog.cancel( + rx.button("Cancel", variant="soft", color_scheme="gray"), + ), + rx.alert_dialog.action( + rx.button("Revoke access", color_scheme="red", variant="solid"), + ), + spacing="3", + margin_top="16px", + justify="end", + ), + style={"max_width": 450}, + ), + ), + spacing="3", +) +``` diff --git a/docs/library/overlay/contextmenu.md b/docs/library/overlay/contextmenu.md new file mode 100644 index 000000000..51072909e --- /dev/null +++ b/docs/library/overlay/contextmenu.md @@ -0,0 +1,223 @@ +--- +components: + - rx.radix.context_menu.root + - rx.radix.context_menu.item + - rx.radix.context_menu.separator + - rx.radix.context_menu.trigger + - rx.radix.context_menu.content + - rx.radix.context_menu.sub + - rx.radix.context_menu.sub_trigger + - rx.radix.context_menu.sub_content + + +only_low_level: + - True + +ContextMenuRoot: | + lambda **props: rx.radix.themes.context_menu.root( + rx.radix.themes.context_menu.trigger( + rx.radix.themes.text("Context Menu (right click)") + ), + rx.radix.themes.context_menu.content( + rx.radix.themes.context_menu.item("Copy", shortcut="⌘ C"), + rx.radix.themes.context_menu.item("Share"), + rx.radix.themes.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.radix.themes.context_menu.sub( + rx.radix.themes.context_menu.sub_trigger("More"), + rx.radix.themes.context_menu.sub_content( + rx.radix.themes.context_menu.item("Eradicate"), + rx.radix.themes.context_menu.item("Duplicate"), + rx.radix.themes.context_menu.item("Archive"), + ), + ), + ), + **props + ) + +ContextMenuTrigger: | + lambda **props: rx.radix.themes.context_menu.root( + rx.radix.themes.context_menu.trigger( + rx.radix.themes.text("Context Menu (right click)"), + **props + ), + rx.radix.themes.context_menu.content( + rx.radix.themes.context_menu.item("Copy", shortcut="⌘ C"), + rx.radix.themes.context_menu.item("Share"), + rx.radix.themes.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.radix.themes.context_menu.sub( + rx.radix.themes.context_menu.sub_trigger("More"), + rx.radix.themes.context_menu.sub_content( + rx.radix.themes.context_menu.item("Eradicate"), + rx.radix.themes.context_menu.item("Duplicate"), + rx.radix.themes.context_menu.item("Archive"), + ), + ), + ), + ) + +ContextMenuContent: | + lambda **props: rx.radix.themes.context_menu.root( + rx.radix.themes.context_menu.trigger( + rx.radix.themes.text("Context Menu (right click)") + ), + rx.radix.themes.context_menu.content( + rx.radix.themes.context_menu.item("Copy", shortcut="⌘ C"), + rx.radix.themes.context_menu.item("Share"), + rx.radix.themes.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.radix.themes.context_menu.sub( + rx.radix.themes.context_menu.sub_trigger("More"), + rx.radix.themes.context_menu.sub_content( + rx.radix.themes.context_menu.item("Eradicate"), + rx.radix.themes.context_menu.item("Duplicate"), + rx.radix.themes.context_menu.item("Archive"), + ), + ), + **props + ), + ) + +ContextMenuSubTrigger: | + lambda **props: rx.radix.themes.context_menu.root( + rx.radix.themes.context_menu.trigger( + rx.radix.themes.text("Context Menu (right click)") + ), + rx.radix.themes.context_menu.content( + rx.radix.themes.context_menu.item("Copy", shortcut="⌘ C"), + rx.radix.themes.context_menu.item("Share"), + rx.radix.themes.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.radix.themes.context_menu.sub( + rx.radix.themes.context_menu.sub_trigger("More", **props), + rx.radix.themes.context_menu.sub_content( + rx.radix.themes.context_menu.item("Eradicate"), + rx.radix.themes.context_menu.item("Duplicate"), + rx.radix.themes.context_menu.item("Archive"), + ), + ), + ), + ) + +ContextMenuSubContent: | + lambda **props: rx.radix.themes.context_menu.root( + rx.radix.themes.context_menu.trigger( + rx.radix.themes.text("Context Menu (right click)") + ), + rx.radix.themes.context_menu.content( + rx.radix.themes.context_menu.item("Copy", shortcut="⌘ C"), + rx.radix.themes.context_menu.item("Share"), + rx.radix.themes.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.radix.themes.context_menu.sub( + rx.radix.themes.context_menu.sub_trigger("More"), + rx.radix.themes.context_menu.sub_content( + rx.radix.themes.context_menu.item("Eradicate"), + rx.radix.themes.context_menu.item("Duplicate"), + rx.radix.themes.context_menu.item("Archive"), + **props + ), + ), + ), + ) + +ContextMenuItem: | + lambda **props: rx.radix.themes.context_menu.root( + rx.radix.themes.context_menu.trigger( + rx.radix.themes.text("Context Menu (right click)") + ), + rx.radix.themes.context_menu.content( + rx.radix.themes.context_menu.item("Copy", shortcut="⌘ C", **props), + rx.radix.themes.context_menu.item("Share", **props), + rx.radix.themes.context_menu.item("Delete", shortcut="⌘ ⌫", color="red", **props), + rx.radix.themes.context_menu.sub( + rx.radix.themes.context_menu.sub_trigger("More"), + rx.radix.themes.context_menu.sub_content( + rx.radix.themes.context_menu.item("Eradicate", **props), + rx.radix.themes.context_menu.item("Duplicate", **props), + rx.radix.themes.context_menu.item("Archive", **props), + ), + ), + ), + ) +--- + + +```python exec +import reflex as rx +``` + +# Context Menu + +A Context Menu is a popup menu that appears upon user interaction, such as a right-click or a hover. + +## Basic Usage + +A Context Menu is composed of a `context_menu.root`, a `context_menu.trigger` and a `context_menu.content`. The `context_menu_root` contains all the parts of a context menu. The `context_menu.trigger` is the element that the user interacts with to open the menu. It wraps the element that will open the context menu. The `context_menu.content` is the component that pops out when the context menu is open. + +The `context_menu.item` contains the actual context menu items and sits under the `context_menu.content`. + +The `context_menu.sub` contains all the parts of a submenu. There is a `context_menu.sub_trigger`, which is an item that opens a submenu. It must be rendered inside a `context_menu.sub` component. The `context_menu.sub_content` is the component that pops out when a submenu is open. It must also be rendered inside a `context_menu.sub` component. + +The `context_menu.separator` is used to visually separate items in a context menu. + +```python demo +rx.context_menu.root( + rx.context_menu.trigger( + rx.button("Right click me"), + ), + rx.context_menu.content( + rx.context_menu.item("Edit", shortcut="⌘ E"), + rx.context_menu.item("Duplicate", shortcut="⌘ D"), + rx.context_menu.separator(), + rx.context_menu.item("Archive", shortcut="⌘ N"), + rx.context_menu.sub( + rx.context_menu.sub_trigger("More"), + rx.context_menu.sub_content( + rx.context_menu.item("Move to project…"), + rx.context_menu.item("Move to folder…"), + rx.context_menu.separator(), + rx.context_menu.item("Advanced options…"), + ), + ), + rx.context_menu.separator(), + rx.context_menu.item("Share"), + rx.context_menu.item("Add to favorites"), + rx.context_menu.separator(), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red"), + ), +) +``` + +```python demo +rx.grid( + rx.context_menu.root( + rx.context_menu.trigger( + rx.button("Right click me"), + ), + rx.context_menu.content( + rx.context_menu.item("Edit", shortcut="⌘ E"), + rx.context_menu.item("Duplicate", shortcut="⌘ D"), + rx.context_menu.separator(), + rx.context_menu.item("Archive", shortcut="⌘ N"), + rx.context_menu.separator(), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red", + ), + size="1", + ), + ), + rx.context_menu.root( + rx.context_menu.trigger( + rx.button("Right click me"), + ), + rx.context_menu.content( + rx.context_menu.item("Edit", shortcut="⌘ E"), + rx.context_menu.item("Duplicate", shortcut="⌘ D"), + rx.context_menu.separator(), + rx.context_menu.item("Archive", shortcut="⌘ N"), + rx.context_menu.separator(), + rx.context_menu.item("Delete", shortcut="⌘ ⌫", color="red" + ), + size="2", + ), + ), + columns="2", + spacing="3", +) +``` diff --git a/docs/library/overlay/dialog.md b/docs/library/overlay/dialog.md new file mode 100644 index 000000000..98efcc77b --- /dev/null +++ b/docs/library/overlay/dialog.md @@ -0,0 +1,193 @@ +--- +components: + - rx.radix.dialog.root + - rx.radix.dialog.trigger + - rx.radix.dialog.title + - rx.radix.dialog.content + - rx.radix.dialog.description + - rx.radix.dialog.close + +only_low_level: + - True + +DialogRoot: | + lambda **props: rx.dialog.root( + rx.dialog.trigger(rx.button("Open Dialog")), + rx.dialog.content( + rx.dialog.title("Welcome to Reflex!"), + rx.dialog.description( + "This is a dialog component. You can render anything you want in here.", + ), + rx.dialog.close( + rx.button("Close Dialog"), + ), + ), + **props, + ) + +DialogContent: | + lambda **props: rx.dialog.root( + rx.dialog.trigger(rx.button("Open Dialog")), + rx.dialog.content( + rx.dialog.title("Welcome to Reflex!"), + rx.dialog.description( + "This is a dialog component. You can render anything you want in here.", + ), + rx.dialog.close( + rx.button("Close Dialog"), + ), + **props, + ), + ) +--- + + +```python exec +import reflex as rx +``` + +# Dialog + +The `dialog.root` contains all the parts of a dialog. + +The `dialog.trigger` wraps the control that will open the dialog. + +The `dialog.content` contains the content of the dialog. + +The `dialog.title` is a title that is announced when the dialog is opened. + +The `dialog.description` is a description that is announced when the dialog is opened. + +The `dialog.close` wraps the control that will close the dialog. + +```python demo +rx.dialog.root( + rx.dialog.trigger(rx.button("Open Dialog")), + rx.dialog.content( + rx.dialog.title("Welcome to Reflex!"), + rx.dialog.description( + "This is a dialog component. You can render anything you want in here.", + ), + rx.dialog.close( + rx.button("Close Dialog", size="3"), + ), + ), +) +``` + +## In context examples + +```python demo +rx.dialog.root( + rx.dialog.trigger( + rx.button("Edit Profile", size="4") + ), + rx.dialog.content( + rx.dialog.title("Edit Profile"), + rx.dialog.description( + "Change your profile details and preferences.", + size="2", + margin_bottom="16px", + ), + rx.flex( + rx.text("Name", as_="div", size="2", margin_bottom="4px", weight="bold"), + rx.input(default_value="Freja Johnson", placeholder="Enter your name"), + rx.text("Email", as_="div", size="2", margin_bottom="4px", weight="bold"), + rx.input(default_value="freja@example.com", placeholder="Enter your email"), + direction="column", + spacing="3", + ), + rx.flex( + rx.dialog.close( + rx.button("Cancel", color_scheme="gray", variant="soft"), + ), + rx.dialog.close( + rx.button("Save"), + ), + spacing="3", + margin_top="16px", + justify="end", + ), + ), +) +``` + +```python demo +rx.dialog.root( + rx.dialog.trigger(rx.button("View users", size="4")), + rx.dialog.content( + rx.dialog.title("Users"), + rx.dialog.description("The following users have access to this project."), + + rx.inset( + rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Full Name"), + rx.table.column_header_cell("Email"), + rx.table.column_header_cell("Group"), + ), + ), + rx.table.body( + rx.table.row( + rx.table.row_header_cell("Danilo Rosa"), + rx.table.cell("danilo@example.com"), + rx.table.cell("Developer"), + ), + rx.table.row( + rx.table.row_header_cell("Zahra Ambessa"), + rx.table.cell("zahra@example.com"), + rx.table.cell("Admin"), + ), + ), + ), + side="x", + margin_top="24px", + margin_bottom="24px", + ), + rx.flex( + rx.dialog.close( + rx.button("Close", variant="soft", color_scheme="gray"), + ), + spacing="3", + justify="end", + ), + ), +) +``` + +## Events when the Dialog opens or closes + +The `on_open_change` event is called when the `open` state of the dialog changes. It is used in conjunction with the `open` prop, which is passed to the event handler. + +```python demo exec +class DialogState(rx.State): + num_opens: int = 0 + opened: bool = False + + def count_opens(self, value: bool): + self.opened = value + self.num_opens += 1 + + +def dialog_example(): + return rx.flex( + rx.heading(f"Number of times dialog opened or closed: {DialogState.num_opens}"), + rx.heading(f"Dialog open: {DialogState.opened}"), + rx.dialog.root( + rx.dialog.trigger(rx.button("Open Dialog")), + rx.dialog.content( + rx.dialog.title("Welcome to Reflex!"), + rx.dialog.description( + "This is a dialog component. You can render anything you want in here.", + ), + rx.dialog.close( + rx.button("Close Dialog", size="3"), + ), + ), + on_open_change=DialogState.count_opens, + ), + direction="column", + spacing="3", + ) +``` diff --git a/docs/library/overlay/drawer.md b/docs/library/overlay/drawer.md new file mode 100644 index 000000000..469900a8e --- /dev/null +++ b/docs/library/overlay/drawer.md @@ -0,0 +1,45 @@ +--- +components: + - rx.radix.drawer.root + - rx.radix.drawer.trigger + - rx.radix.drawer.overlay + - rx.radix.drawer.portal + - rx.radix.drawer.content + - rx.radix.drawer.close + +only_low_level: + - True +--- + +```python exec +import reflex as rx +``` + + +# Drawer + +```python demo +rx.drawer.root( + rx.drawer.trigger( + rx.button("Open Drawer") + ), + rx.drawer.overlay(), + rx.drawer.portal( + rx.drawer.content( + rx.flex( + rx.drawer.close(rx.box(rx.button("Close"))), + align_items="start", + direction="column", + ), + top="auto", + right="auto", + height="100%", + width="20em", + padding="2em", + background_color="#FFF" + #background_color=rx.color("green", 3) + ) + ), + direction="left", +) +``` \ No newline at end of file diff --git a/docs/library/overlay/dropdownmenu.md b/docs/library/overlay/dropdownmenu.md new file mode 100644 index 000000000..65c437690 --- /dev/null +++ b/docs/library/overlay/dropdownmenu.md @@ -0,0 +1,244 @@ +--- +components: + - rx.radix.dropdown_menu.root + - rx.radix.dropdown_menu.content + - rx.radix.dropdown_menu.trigger + - rx.radix.dropdown_menu.item + - rx.radix.dropdown_menu.separator + - rx.radix.dropdown_menu.sub_content + +only_low_level: + - True + +DropdownMenuRoot: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Share"), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Eradicate"), + rx.menu.item("Duplicate"), + rx.menu.item("Archive"), + ), + ), + ), + **props + ) + +DropdownMenuContent: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Share"), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Eradicate"), + rx.menu.item("Duplicate"), + rx.menu.item("Archive"), + ), + ), + **props, + ), + ) + +DropdownMenuItem: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E", **props), + rx.menu.item("Share", **props), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red", **props), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Eradicate", **props), + rx.menu.item("Duplicate", **props), + rx.menu.item("Archive", **props), + ), + ), + ), + ) + +DropdownMenuSub: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Share"), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Eradicate"), + rx.menu.item("Duplicate"), + rx.menu.item("Archive"), + ), + **props, + ), + ), + ) + +DropdownMenuSubTrigger: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Share"), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.menu.sub( + rx.menu.sub_trigger("More", **props), + rx.menu.sub_content( + rx.menu.item("Eradicate"), + rx.menu.item("Duplicate"), + rx.menu.item("Archive"), + ), + ), + ), + ) + +DropdownMenuSubContent: | + lambda **props: rx.menu.root( + rx.menu.trigger(rx.button("drop down menu")), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Share"), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Eradicate"), + rx.menu.item("Duplicate"), + rx.menu.item("Archive"), + **props, + ), + ), + ), + ) +--- + + + +```python exec +import reflex as rx +``` + +# Dropdown Menu + +A Dropdown Menu is a menu that offers a list of options that a user can select from. They are typically positioned near a button that will control their appearance and disappearance. + +A Dropdown Menu is composed of a `dropdown_menu.root`, a `dropdown_menu.trigger` and a `dropdown_menu.content`. The `dropdown_menu.trigger` is the element that the user interacts with to open the menu. It wraps the element that will open the dropdown menu. The `dropdown_menu.content` is the component that pops out when the dropdown menu is open. + +The `dropdown_menu.item` contains the actual dropdown menu items and sits under the `dropdown_menu.content`. The `shortcut` prop is an optional shortcut command displayed next to the item text. + +The `dropdown_menu.sub` contains all the parts of a submenu. There is a `dropdown_menu.sub_trigger`, which is an item that opens a submenu. It must be rendered inside a `dropdown_menu.sub` component. The `dropdown_menu.sub_component` is the component that pops out when a submenu is open. It must also be rendered inside a `dropdown_menu.sub` component. + +The `dropdown_menu.separator` is used to visually separate items in a dropdown menu. + +```python demo +rx.menu.root( + rx.menu.trigger( + rx.button("Options", variant="soft"), + ), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Duplicate", shortcut="⌘ D"), + rx.menu.separator(), + rx.menu.item("Archive", shortcut="⌘ N"), + rx.menu.sub( + rx.menu.sub_trigger("More"), + rx.menu.sub_content( + rx.menu.item("Move to project…"), + rx.menu.item("Move to folder…"), + rx.menu.separator(), + rx.menu.item("Advanced options…"), + ), + ), + rx.menu.separator(), + rx.menu.item("Share"), + rx.menu.item("Add to favorites"), + rx.menu.separator(), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + ), +) +``` + +# Size + +```python demo +rx.flex( + rx.menu.root( + rx.menu.trigger( + rx.button("Options", variant="soft", size="2"), + ), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Duplicate", shortcut="⌘ D"), + rx.menu.separator(), + rx.menu.item("Archive", shortcut="⌘ N"), + rx.menu.separator(), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + size="2", + ), + ), + rx.menu.root( + rx.menu.trigger( + rx.button("Options", variant="soft", size="1"), + ), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Duplicate", shortcut="⌘ D"), + rx.menu.separator(), + rx.menu.item("Archive", shortcut="⌘ N"), + rx.menu.separator(), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + size="1", + ), + ), + spacing="3", + align="center", +) +``` + +## Events when the Dropdown Menu opens or closes + +The `on_open_change` event, from the `dropdown_menu.root`, is called when the `open` state of the dropdown menu changes. It is used in conjunction with the `open` prop, which is passed to the event handler. + +```python demo exec +class DropdownMenuState(rx.State): + num_opens: int = 0 + opened: bool = False + + def count_opens(self, value: bool): + self.opened = value + self.num_opens += 1 + + +def dropdown_menu_example(): + return rx.flex( + rx.heading(f"Number of times Dropdown Menu opened or closed: {DropdownMenuState.num_opens}"), + rx.heading(f"Dropdown Menu open: {DropdownMenuState.opened}"), + rx.menu.root( + rx.menu.trigger( + rx.button("Options", variant="soft", size="2"), + ), + rx.menu.content( + rx.menu.item("Edit", shortcut="⌘ E"), + rx.menu.item("Duplicate", shortcut="⌘ D"), + rx.menu.separator(), + rx.menu.item("Archive", shortcut="⌘ N"), + rx.menu.separator(), + rx.menu.item("Delete", shortcut="⌘ ⌫", color="red"), + ), + on_open_change=DropdownMenuState.count_opens, + ), + direction="column", + spacing="3", + ) +``` diff --git a/docs/library/overlay/hovercard.md b/docs/library/overlay/hovercard.md new file mode 100644 index 000000000..0c6050da5 --- /dev/null +++ b/docs/library/overlay/hovercard.md @@ -0,0 +1,125 @@ +--- +components: + - rx.radix.hover_card.root + - rx.radix.hover_card.content + - rx.radix.hover_card.trigger + +only_low_level: + - True + +HoverCardRoot: | + lambda **props: rx.radix.themes.hover_card.root( + rx.radix.themes.hover_card.trigger( + rx.radix.themes.link("Hover over me"), + ), + rx.radix.themes.hover_card.content( + rx.radix.themes.text("This is the tooltip content."), + ), + **props + ) + +HoverCardContent: | + lambda **props: rx.radix.themes.hover_card.root( + rx.radix.themes.hover_card.trigger( + rx.radix.themes.link("Hover over me"), + ), + rx.radix.themes.hover_card.content( + rx.radix.themes.text("This is the tooltip content."), + **props + ), + ) +--- + +```python exec +import reflex as rx +``` + +# Hovercard + +The `hover_card.root` contains all the parts of a hover card. + +The `hover_card.trigger` wraps the link that will open the hover card. + +The `hover_card.content` contains the content of the open hover card. + +```python demo +rx.text( + "Hover over the text to see the tooltip. ", + rx.hover_card.root( + rx.hover_card.trigger( + rx.link("Hover over me", color_scheme="blue", underline="always"), + ), + rx.hover_card.content( + rx.text("This is the tooltip content."), + ), + ), +) +``` + +```python demo +rx.text( + "Hover over the text to see the tooltip. ", + rx.hover_card.root( + rx.hover_card.trigger( + rx.link("Hover over me", color_scheme="blue", underline="always"), + ), + rx.hover_card.content( + rx.grid( + rx.inset( + side="left", + pr="current", + background="url('https://source.unsplash.com/random/800x600') center/cover", + height="full", + ), + rx.box( + rx.text_area(placeholder="Write a comment…", style={"height": 80}), + rx.flex( + rx.checkbox("Send to group"), + spacing="3", + margin_top="12px", + justify="between", + ), + padding_left="12px", + ), + columns="120px 1fr", + ), + style={"width": 360}, + ), + ), +) +``` + +## Events when the Hovercard opens or closes + +The `on_open_change` event is called when the `open` state of the hovercard changes. It is used in conjunction with the `open` prop, which is passed to the event handler. + +```python demo exec +class HovercardState(rx.State): + num_opens: int = 0 + opened: bool = False + + def count_opens(self, value: bool): + self.opened = value + self.num_opens += 1 + + +def hovercard_example(): + return rx.flex( + rx.heading(f"Number of times hovercard opened or closed: {HovercardState.num_opens}"), + rx.heading(f"Hovercard open: {HovercardState.opened}"), + rx.text( + "Hover over the text to see the tooltip. ", + rx.hover_card.root( + rx.hover_card.trigger( + rx.link("Hover over me", color_scheme="blue", underline="always"), + ), + rx.hover_card.content( + rx.text("This is the tooltip content."), + ), + on_open_change=HovercardState.count_opens, + ), + ), + direction="column", + spacing="3", + ) +``` diff --git a/docs/library/overlay/popover.md b/docs/library/overlay/popover.md new file mode 100644 index 000000000..a3053f58b --- /dev/null +++ b/docs/library/overlay/popover.md @@ -0,0 +1,146 @@ +--- +components: + - rx.radix.popover.root + - rx.radix.popover.content + - rx.radix.popover.trigger + - rx.radix.popover.close + +only_low_level: + - True + +PopoverRoot: | + lambda **props: rx.radix.themes.popover.root( + rx.radix.themes.popover.trigger( + rx.radix.themes.button("Popover"), + ), + rx.radix.themes.popover.content( + rx.radix.themes.flex( + rx.radix.themes.text("Simple Example"), + rx.radix.themes.popover.close( + rx.radix.themes.button("Close"), + ), + direction="column", + spacing="3", + ), + ), + **props + ) + +PopoverContent: | + lambda **props: rx.radix.themes.popover.root( + rx.radix.themes.popover.trigger( + rx.radix.themes.button("Popover"), + ), + rx.radix.themes.popover.content( + rx.radix.themes.flex( + rx.radix.themes.text("Simple Example"), + rx.radix.themes.popover.close( + rx.radix.themes.button("Close"), + ), + direction="column", + spacing="3", + ), + **props + ), + ) +--- + +```python exec +import reflex as rx +``` + +# Popover + +A popover displays content, triggered by a button. + +The `popover.root` contains all the parts of a popover. + +The `popover.trigger` contains the button that toggles the popover. + +The `popover.content` is the component that pops out when the popover is open. + +The `popover.close` is the button that closes an open popover. + +## Basic Example + +```python demo +rx.popover.root( + rx.popover.trigger( + rx.button("Popover"), + ), + rx.popover.content( + rx.flex( + rx.text("Simple Example"), + rx.popover.close( + rx.button("Close"), + ), + direction="column", + spacing="3", + ), + ), +) +``` + +## Examples in Context + +```python demo + +rx.popover.root( + rx.popover.trigger( + rx.button("Comment", variant="soft"), + ), + rx.popover.content( + rx.flex( + rx.avatar( + "2", + fallback="RX", + radius="full" + ), + rx.box( + rx.text_area(placeholder="Write a comment…", style={"height": 80}), + rx.flex( + rx.checkbox("Send to group"), + rx.popover.close( + rx.button("Comment", size="1") + ), + spacing="3", + margin_top="12px", + justify="between", + ), + flex_grow="1", + ), + spacing="3" + ), + style={"width": 360}, + ) +) +``` + +```python demo +rx.popover.root( + rx.popover.trigger( + rx.button("Feedback", variant="classic"), + ), + rx.popover.content( + rx.inset( + side="top", + background="url('https://source.unsplash.com/random/800x600') center/cover", + height="100px", + ), + rx.box( + rx.text_area(placeholder="Write a comment…", style={"height": 80}), + rx.flex( + rx.checkbox("Send to group"), + rx.popover.close( + rx.button("Comment", size="1") + ), + spacing="3", + margin_top="12px", + justify="between", + ), + padding_top="12px", + ), + style={"width": 360}, + ) +) +``` diff --git a/docs/library/overlay/tooltip.md b/docs/library/overlay/tooltip.md new file mode 100644 index 000000000..006148be0 --- /dev/null +++ b/docs/library/overlay/tooltip.md @@ -0,0 +1,28 @@ +--- +components: + - rx.radix.tooltip + +Tooltip: | + lambda **props: rx.radix.themes.tooltip( + rx.radix.themes.button("Hover over me"), + content="This is the tooltip content.", + **props, + ) +--- + +```python exec +import reflex as rx +``` + +# Tooltip + +A `tooltip` displays informative information when users hover over or focus on an element. + +It takes a `content` prop, which is the content associated with the tooltip. + +```python demo +rx.tooltip( + rx.button("Hover over me"), + content="This is the tooltip content.", +) +``` diff --git a/docs/library/theming/theme.md b/docs/library/theming/theme.md new file mode 100644 index 000000000..edfb15321 --- /dev/null +++ b/docs/library/theming/theme.md @@ -0,0 +1,16 @@ +--- + components: + - rx.theme +--- + +# Theme + + The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in the rx.App. + + ```python + app = rx.App( + theme=rx.theme( + appearance="light", has_background=True, radius="large", accent_color="teal" + ) + ) + ``` diff --git a/docs/library/theming/theme_panel.md b/docs/library/theming/theme_panel.md new file mode 100644 index 000000000..86ec2477c --- /dev/null +++ b/docs/library/theming/theme_panel.md @@ -0,0 +1,18 @@ +--- +components: + - rx.theme_panel +--- + +# Theme Panel + +The `ThemePanel` component is a container for the `Theme` component. It provides a way to change the theme of the application. The `ThemePanel` component is a part of the `rx.radix.themes` package. + +```python +rx.theme_panel() +``` + +The theme panel is closed by default. You can set it open `default_open=True`. + +```python +rx.theme_panel(default_open=True) +``` diff --git a/docs/library/typography/blockquote.md b/docs/library/typography/blockquote.md new file mode 100644 index 000000000..d1b06391d --- /dev/null +++ b/docs/library/typography/blockquote.md @@ -0,0 +1,77 @@ +--- +components: + - rx.radix.blockquote +--- + +```python exec +import reflex as rx +``` + +# Blockquote + +```python demo +rx.blockquote("Perfect typography is certainly the most elusive of all arts.") +``` + +## Size + +Use the `size` prop to control the size of the blockquote. The prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. + +```python demo +rx.flex( + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="1"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="2"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="3"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="4"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="5"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="6"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="7"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="8"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", size="9"), + direction="column", + spacing="3", +) +``` + +## Weight + +Use the `weight` prop to set the blockquote weight. + +```python demo +rx.flex( + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="light"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="regular"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="medium"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", weight="bold"), + direction="column", + spacing="3", +) +``` + +## Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="indigo"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="cyan"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="crimson"), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", color_scheme="orange"), + direction="column", + spacing="3", +) +``` + +## High Contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.flex( + rx.blockquote("Perfect typography is certainly the most elusive of all arts."), + rx.blockquote("Perfect typography is certainly the most elusive of all arts.", high_contrast=True), + direction="column", + spacing="3", +) +``` diff --git a/docs/library/typography/code.md b/docs/library/typography/code.md new file mode 100644 index 000000000..d9d7e6129 --- /dev/null +++ b/docs/library/typography/code.md @@ -0,0 +1,110 @@ +--- +components: + - rx.radix.code +--- + +```python exec +import reflex as rx +``` + +# Code + +```python demo +rx.code("console.log()") +``` + +## Size + +Use the `size` prop to control text size. This prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. + +```python demo +rx.flex( + rx.code("console.log()", size="1"), + rx.code("console.log()", size="2"), + rx.code("console.log()", size="3"), + rx.code("console.log()", size="4"), + rx.code("console.log()", size="5"), + rx.code("console.log()", size="6"), + rx.code("console.log()", size="7"), + rx.code("console.log()", size="8"), + rx.code("console.log()", size="9"), + direction="column", + spacing="3", + align="start", +) +``` + +## Weight + +Use the `weight` prop to set the text weight. + +```python demo +rx.flex( + rx.code("console.log()", weight="light"), + rx.code("console.log()", weight="regular"), + rx.code("console.log()", weight="medium"), + rx.code("console.log()", weight="bold"), + direction="column", + spacing="3", +) +``` + +## Variant + +Use the `variant` prop to control the visual style. + +```python demo +rx.flex( + rx.code("console.log()", variant="solid"), + rx.code("console.log()", variant="soft"), + rx.code("console.log()", variant="outline"), + rx.code("console.log()", variant="ghost"), + direction="column", + spacing="2", + align="start", +) +``` + +## Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.code("console.log()", color_scheme="indigo"), + rx.code("console.log()", color_scheme="crimson"), + rx.code("console.log()", color_scheme="orange"), + rx.code("console.log()", color_scheme="cyan"), + direction="column", + spacing="2", + align="start", +) +``` + +## High Contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.flex( + rx.flex( + rx.code("console.log()", variant="solid"), + rx.code("console.log()", variant="soft"), + rx.code("console.log()", variant="outline"), + rx.code("console.log()", variant="ghost"), + direction="column", + align="start", + spacing="2", + ), + rx.flex( + rx.code("console.log()", variant="solid", high_contrast=True), + rx.code("console.log()", variant="soft", high_contrast=True), + rx.code("console.log()", variant="outline", high_contrast=True), + rx.code("console.log()", variant="ghost", high_contrast=True), + direction="column", + align="start", + spacing="2", + ), + spacing="3", +) +``` diff --git a/docs/library/typography/em.md b/docs/library/typography/em.md new file mode 100644 index 000000000..fb6df98dc --- /dev/null +++ b/docs/library/typography/em.md @@ -0,0 +1,16 @@ +--- +components: + - rx.radix.text.em +--- + +```python exec +import reflex as rx +``` + +# Em (Emphasis) + +Marks text to stress emphasis. + +```python demo +rx.text("We ", rx.text.em("had"), " to do something about it.") +``` diff --git a/docs/library/typography/heading.md b/docs/library/typography/heading.md new file mode 100644 index 000000000..bfc10c1f5 --- /dev/null +++ b/docs/library/typography/heading.md @@ -0,0 +1,152 @@ +--- +components: + - rx.radix.heading +--- + +```python exec +import reflex as rx +``` + +# Heading + +```python demo +rx.heading("The quick brown fox jumps over the lazy dog.") +``` + +## As another element + +Use the `as_` prop to change the heading level. This prop is purely semantic and does not change the visual appearance. + +```python demo +rx.flex( + rx.heading("Level 1", as_="h1"), + rx.heading("Level 2", as_="h2"), + rx.heading("Level 3", as_="h3"), + direction="column", + spacing="3", +) +``` + +## Size + +Use the `size` prop to control the size of the heading. The prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease + +```python demo +rx.flex( + rx.heading("The quick brown fox jumps over the lazy dog.", size="1"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="2"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="3"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="4"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="5"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="6"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="7"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="8"), + rx.heading("The quick brown fox jumps over the lazy dog.", size="9"), + direction="column", + spacing="3", +) +``` + +## Weight + +Use the `weight` prop to set the text weight. + +```python demo +rx.flex( + rx.heading("The quick brown fox jumps over the lazy dog.", weight="light"), + rx.heading("The quick brown fox jumps over the lazy dog.", weight="regular"), + rx.heading("The quick brown fox jumps over the lazy dog.", weight="medium"), + rx.heading("The quick brown fox jumps over the lazy dog.", weight="bold"), + direction="column", + spacing="3", +) +``` + +## Align + +Use the `align` prop to set text alignment. + +```python demo +rx.flex( + rx.heading("Left-aligned", align="left"), + rx.heading("Center-aligned", align="center"), + rx.heading("Right-aligned", align="right"), + direction="column", + spacing="3", + width="100%", +) +``` + +## Trim + +Use the `trim` prop to trim the leading space at the start, end, or both sides of the text. + +```python demo +rx.flex( + rx.heading("Without Trim", + trim="normal", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + rx.heading("With Trim", + trim="both", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + direction="column", + spacing="3", +) +``` + +Trimming the leading is useful when dialing in vertical spacing in cards or other “boxy” components. Otherwise, padding looks larger on top and bottom than on the sides. + +```python demo +rx.flex( + rx.box( + rx.heading("Without trim", margin_bottom="4px", size="3",), + rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), + style={"background": "var(--gray-a2)", + "border": "1px dashed var(--gray-a7)",}, + padding="16px", + ), + rx.box( + rx.heading("With trim", margin_bottom="4px", size="3", trim="start"), + rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), + style={"background": "var(--gray-a2)", + "border": "1px dashed var(--gray-a7)",}, + padding="16px", + ), + direction="column", + spacing="3", +) +``` + +## Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="indigo"), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="cyan"), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="crimson"), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="orange"), + direction="column", +) +``` + +## High Contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.flex( + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="indigo", high_contrast=True), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="cyan", high_contrast=True), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="crimson", high_contrast=True), + rx.heading("The quick brown fox jumps over the lazy dog.", color_scheme="orange", high_contrast=True), + direction="column", +) +``` diff --git a/docs/library/typography/kbd.md b/docs/library/typography/kbd.md new file mode 100644 index 000000000..12cd1b223 --- /dev/null +++ b/docs/library/typography/kbd.md @@ -0,0 +1,36 @@ +--- +components: + - rx.radix.text.kbd +--- + +```python exec +import reflex as rx +``` + +# rx.text.kbd (Keyboard) + +Represents keyboard input or a hotkey. + +```python demo +rx.text.kbd("Shift + Tab") +``` + +## Size + +Use the `size` prop to control text size. This prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. + +```python demo +rx.flex( + rx.text.kbd("Shift + Tab", size="1"), + rx.text.kbd("Shift + Tab", size="2"), + rx.text.kbd("Shift + Tab", size="3"), + rx.text.kbd("Shift + Tab", size="4"), + rx.text.kbd("Shift + Tab", size="5"), + rx.text.kbd("Shift + Tab", size="6"), + rx.text.kbd("Shift + Tab", size="7"), + rx.text.kbd("Shift + Tab", size="8"), + rx.text.kbd("Shift + Tab", size="9"), + direction="column", + spacing="3", +) +``` diff --git a/docs/library/typography/link.md b/docs/library/typography/link.md new file mode 100644 index 000000000..1a2a1cd06 --- /dev/null +++ b/docs/library/typography/link.md @@ -0,0 +1,140 @@ +--- +components: + - rx.radix.link +--- + +```python exec +import reflex as rx +``` + +# Link + +Links are accessible elements used primarily for navigation. Use the `href` prop to specify the location for the link to navigate to. + +```python demo +rx.link("Reflex Home Page.", href="https://reflex.dev") +``` + +You can also provide local links to other pages in your project without writing the full url. + +```python demo +rx.link("Example", href="/docs/library",) +``` + +The `link` component can be used to wrap other components to make them link to other pages. + +```python demo +rx.link(rx.button("Example"), href="https://reflex.dev") +``` + +You can also create anchors to link to specific parts of a page using the `id` prop. + +```python demo +rx.box("Example", id="example") +``` + +To reference an anchor, you can use the `href` prop of the `link` component. The `href` should be in the format of the page you want to link to followed by a # and the id of the anchor. + +```python demo +rx.link("Example", href="/docs/library/typography/link#example") +``` + +# Style + +## Size + +Use the `size` prop to control the size of the link. The prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. + +```python demo +rx.flex( + rx.link("The quick brown fox jumps over the lazy dog.", size="1"), + rx.link("The quick brown fox jumps over the lazy dog.", size="2"), + rx.link("The quick brown fox jumps over the lazy dog.", size="3"), + rx.link("The quick brown fox jumps over the lazy dog.", size="4"), + rx.link("The quick brown fox jumps over the lazy dog.", size="5"), + rx.link("The quick brown fox jumps over the lazy dog.", size="6"), + rx.link("The quick brown fox jumps over the lazy dog.", size="7"), + rx.link("The quick brown fox jumps over the lazy dog.", size="8"), + rx.link("The quick brown fox jumps over the lazy dog.", size="9"), + direction="column", + spacing="3", +) +``` + +## Weight + +Use the `weight` prop to set the text weight. + +```python demo +rx.flex( + rx.link("The quick brown fox jumps over the lazy dog.", weight="light"), + rx.link("The quick brown fox jumps over the lazy dog.", weight="regular"), + rx.link("The quick brown fox jumps over the lazy dog.", weight="medium"), + rx.link("The quick brown fox jumps over the lazy dog.", weight="bold"), + direction="column", + spacing="3", +) +``` + +## Trim + +Use the `trim` prop to trim the leading space at the start, end, or both sides of the rendered text. + +```python demo +rx.flex( + rx.link("Without Trim", + trim="normal", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + rx.link("With Trim", + trim="both", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + direction="column", + spacing="3", +) +``` + +## Underline + +Use the `underline` prop to manage the visibility of the underline affordance. It defaults to `auto`. + +```python demo +rx.flex( + rx.link("The quick brown fox jumps over the lazy dog.", underline="auto"), + rx.link("The quick brown fox jumps over the lazy dog.", underline="hover"), + rx.link("The quick brown fox jumps over the lazy dog.", underline="always"), + direction="column", + spacing="3", +) +``` + +## Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="indigo"), + rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="cyan"), + rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="crimson"), + rx.link("The quick brown fox jumps over the lazy dog.", color_scheme="orange"), + direction="column", +) +``` + +## High Contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.flex( + rx.link("The quick brown fox jumps over the lazy dog."), + rx.link("The quick brown fox jumps over the lazy dog.", high_contrast=True), + direction="column", +) +``` diff --git a/docs/library/typography/markdown.md b/docs/library/typography/markdown.md new file mode 100644 index 000000000..73735d79f --- /dev/null +++ b/docs/library/typography/markdown.md @@ -0,0 +1,113 @@ +--- +components: + - rx.markdown +--- + +```python exec +import reflex as rx +``` + +# Markdown + +The `rx.markdown` component can be used to render markdown text. +It is based on [Github Flavored Markdown](https://github.github.com/gfm/). + +```python demo +rx.vstack( + rx.markdown("# Hello World!"), + rx.markdown("## Hello World!"), + rx.markdown("### Hello World!"), + rx.markdown("Support us on [Github](https://github.com/reflex-dev/reflex)."), + rx.markdown("Use `reflex deploy` to deploy your app with **a single command**."), +) +``` + +## Math Equations + +You can render math equations using LaTeX. +For inline equations, surround the equation with `$`: + +```python demo +rx.markdown("Pythagorean theorem: $a^2 + b^2 = c^2$.") +``` + +## Syntax Highlighting + +You can render code blocks with syntax highlighting using the \`\`\`\{language} syntax: + +```python demo +rx.markdown( +r""" +\```python +import reflex as rx +from .pages import index + +app = rx.App() +app.add_page(index) +\``` +""" +) +``` + +## Tables + +You can render tables using the `|` syntax: + +```python demo +rx.markdown( + """ +| Syntax | Description | +| ----------- | ----------- | +| Header | Title | +| Paragraph | Text | +""" +) +``` + +## Component Map + +You can specify which components to use for rendering markdown elements using the +`component_map` prop. + +Each key in the `component_map` prop is a markdown element, and the value is +a function that takes the text of the element as input and returns a Reflex component. + +```md alert +The `codeblock` and `a` tags are special cases. In addition to the `text`, they also receive a `props` argument containing additional props for the component. +``` + +```python demo exec +component_map = { + "h1": lambda text: rx.chakra.heading(text, size="lg", margin_y="1em"), + "h2": lambda text: rx.chakra.heading(text, size="md", margin_y="1em"), + "h3": lambda text: rx.chakra.heading(text, size="sm", margin_y="1em"), + "p": lambda text: rx.text(text, color="green", margin_y="1em"), + "code": lambda text: rx.code(text, color="purple"), + "codeblock": lambda text, **props: rx.code_block(text, **props, theme="dark", margin_y="1em"), + "a": lambda text, **props: rx.link(text, **props, color="blue", _hover={"color": "red"}), +} + +def index(): + return rx.box( + rx.markdown( +r""" +# Hello World! + +## This is a Subheader + +### And Another Subheader + +Here is some `code`: + +\```python +import reflex as rx + +component = rx.text("Hello World!") +\``` + +And then some more text here, followed by a link to [Reflex](https://reflex.dev). +""", + component_map=component_map, +) + ) +``` diff --git a/docs/library/typography/quote.md b/docs/library/typography/quote.md new file mode 100644 index 000000000..a5d612130 --- /dev/null +++ b/docs/library/typography/quote.md @@ -0,0 +1,19 @@ +--- +components: + - rx.radix.text.quote +--- + +```python exec +import reflex as rx +``` + +# Quote + +A short inline quotation. + +```python demo +rx.text("His famous quote, ", + rx.text.quote("Styles come and go. Good design is a language, not a style"), + ", elegantly sums up Massimo’s philosophy of design." + ) +``` diff --git a/docs/library/typography/strong.md b/docs/library/typography/strong.md new file mode 100644 index 000000000..d409ac9ac --- /dev/null +++ b/docs/library/typography/strong.md @@ -0,0 +1,16 @@ +--- +components: + - rx.radix.text.strong +--- + +```python exec +import reflex as rx +``` + +# Strong + +Marks text to signify strong importance. + +```python demo +rx.text("The most important thing to remember is, ", rx.text.strong("stay positive"), ".") +``` diff --git a/docs/library/typography/text.md b/docs/library/typography/text.md new file mode 100644 index 000000000..4780b2df1 --- /dev/null +++ b/docs/library/typography/text.md @@ -0,0 +1,194 @@ +--- +components: + - rx.radix.text + - rx.radix.text.em + +--- + +```python exec +import reflex as rx +``` + +# Text + +```python demo +rx.text("The quick brown fox jumps over the lazy dog.") +``` + +## As another element + +Use the `as_` prop to render text as a `p`, `label`, `div` or `span`. This prop is purely semantic and does not alter visual appearance. + +```python demo +rx.flex( + rx.text("This is a ", rx.text.strong("paragraph"), " element.", as_="p"), + rx.text("This is a ", rx.text.strong("label"), " element.", as_="label"), + rx.text("This is a ", rx.text.strong("div"), " element.", as_="div"), + rx.text("This is a ", rx.text.strong("span"), " element.", as_="span"), + direction="column", + spacing="3", +) +``` + +## Size + +Use the `size` prop to control text size. This prop also provides correct line height and corrective letter spacing—as text size increases, the relative line height and letter spacing decrease. + +```python demo +rx.flex( + rx.text("The quick brown fox jumps over the lazy dog.", size="1"), + rx.text("The quick brown fox jumps over the lazy dog.", size="2"), + rx.text("The quick brown fox jumps over the lazy dog.", size="3"), + rx.text("The quick brown fox jumps over the lazy dog.", size="4"), + rx.text("The quick brown fox jumps over the lazy dog.", size="5"), + rx.text("The quick brown fox jumps over the lazy dog.", size="6"), + rx.text("The quick brown fox jumps over the lazy dog.", size="7"), + rx.text("The quick brown fox jumps over the lazy dog.", size="8"), + rx.text("The quick brown fox jumps over the lazy dog.", size="9"), + direction="column", + spacing="3", +) +``` + +Sizes 2–4 are designed to work well for long-form content. Sizes 1–3 are designed to work well for UI labels. + +## Weight + +Use the `weight` prop to set the text weight. + +```python demo +rx.flex( + rx.text("The quick brown fox jumps over the lazy dog.", weight="light", as_="div"), + rx.text("The quick brown fox jumps over the lazy dog.", weight="regular", as_="div"), + rx.text("The quick brown fox jumps over the lazy dog.", weight="medium", as_="div"), + rx.text("The quick brown fox jumps over the lazy dog.", weight="bold", as_="div"), + direction="column", + spacing="3", +) +``` + +## Align + +Use the `align` prop to set text alignment. + +```python demo +rx.flex( + rx.text("Left-aligned", align="left", as_="div"), + rx.text("Center-aligned", align="center", as_="div"), + rx.text("Right-aligned", align="right", as_="div"), + direction="column", + spacing="3", + width="100%", +) +``` + +## Trim + +Use the `trim` prop to trim the leading space at the start, end, or both sides of the text box. + +```python demo +rx.flex( + rx.text("Without Trim", + trim="normal", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + rx.text("With Trim", + trim="both", + style={"background": "var(--gray-a2)", + "border_top": "1px dashed var(--gray-a7)", + "border_bottom": "1px dashed var(--gray-a7)",} + ), + direction="column", + spacing="3", +) +``` + +Trimming the leading is useful when dialing in vertical spacing in cards or other “boxy” components. Otherwise, padding looks larger on top and bottom than on the sides. + +```python demo +rx.flex( + rx.box( + rx.heading("Without trim", margin_bottom="4px", size="3",), + rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), + style={"background": "var(--gray-a2)", + "border": "1px dashed var(--gray-a7)",}, + padding="16px", + ), + rx.box( + rx.heading("With trim", margin_bottom="4px", size="3", trim="start"), + rx.text("The goal of typography is to relate font size, line height, and line width in a proportional way that maximizes beauty and makes reading easier and more pleasant."), + style={"background": "var(--gray-a2)", + "border": "1px dashed var(--gray-a7)",}, + padding="16px", + ), + direction="column", + spacing="3", +) +``` + +## Color + +Use the `color_scheme` prop to assign a specific color, ignoring the global theme. + +```python demo +rx.flex( + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="indigo"), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="cyan"), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="crimson"), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="orange"), + direction="column", +) +``` + +## High Contrast + +Use the `high_contrast` prop to increase color contrast with the background. + +```python demo +rx.flex( + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="indigo", high_contrast=True), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="cyan", high_contrast=True), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="crimson", high_contrast=True), + rx.text("The quick brown fox jumps over the lazy dog.", color_scheme="orange", high_contrast=True), + direction="column", +) +``` + +## With formatting + +Compose `Text` with formatting components to add emphasis and structure to content. + +```python demo +rx.text( + "Look, such a helpful ", + rx.link("link", href="#"), + ", an ", + rx.text.em("italic emphasis"), + " a piece of computer ", + rx.code("code"), + ", and even a hotkey combination ", + rx.text.kbd("⇧⌘A"), + " within the text.", + size="5", +) +``` + +## With form controls + +Composing `text` with a form control like `checkbox`, `radiogroup`, or `switch` automatically centers the control with the first line of text, even when the text is multi-line. + +```python demo +rx.box( + rx.text( + rx.flex( + rx.checkbox(default_checked=True), + "I understand that these documents are confidential and cannot be shared with a third party.", + ), + as_="label", + size="3", + ), + style={"max_width": 300}, +) +``` diff --git a/docs/pages/dynamic_routing.md b/docs/pages/dynamic_routing.md new file mode 100644 index 000000000..38a105836 --- /dev/null +++ b/docs/pages/dynamic_routing.md @@ -0,0 +1,132 @@ +```python exec +import reflex as rx +from pcweb import constants, styles + +dynamic_routes = ( +""" +class State(rx.State): + @rx.var + def post_id(self) -> str: + return self.router.page.params.get('pid', 'no pid') + +@rx.page(route='/post/[pid]') +def post(): + \'''A page that updates based on the route.\''' + return rx.heading(State.post_id) + +app = rx.App() +""" +) + + +catch_all_route = ( +""" +class State(rx.State): + @rx.var + def user_post(self) -> str: + args = self.router.page.params + usernames = args.get('username', []) + return f'Posts by {', '.join(usernames)}' + +@rx.page(route='/users/[id]/posts/[...username]') +def post(): + return rx.center( + rx.text(State.user_post) + ) + + +app = rx.App() +""" +) + + +optional_catch_all_route = ( +""" +class State(rx.State): + @rx.var + def user_post(self) -> str: + args = self.router.page.params + usernames = args.get('username', []) + return f'Posts by {', '.join(usernames)}' + +@rx.page(route='/users/[id]/posts/[[...username]]') +def post(): + return rx.center( + rx.text(State.user_post) + ) + + +app = rx.App() +""" +) +``` + +# Dynamic Routes + +Dynamic routes in Reflex allow you to handle varying URL structures, enabling you to create flexible +and adaptable web applications. This section covers regular dynamic routes, catch-all routes, +and optional catch-all routes, each with detailed examples. + +## Regular Dynamic Routes + +Regular dynamic routes in Reflex allow you to match specific segments in a URL dynamically. + +Example: + +```python +{dynamic_routes} +``` + +In this case, a route like `/user/john/posts/5` would display "Posts by john: Post 5". + +## Catch-All Routes + +Catch-all routes in Reflex allow you to match any number of segments in a URL dynamically. + +Example: + +```python +{catch_all_route} +``` + +In this case, the `...username` catch-all pattern captures any number of segments after +`/users/`, allowing URLs like `/users/2/john/` and `/users/1/john/doe/` to match the route. + +## Optional Catch-All Routes + +Optional catch-all routes, enclosed in double square brackets (`[[...]]`). This indicates that the specified segments +are optional, and the route can match URLs with or without those segments. + +Example: + +```python +{optional_catch_all_route} +``` + +Optional catch-all routes allow matching URLs with or without specific segments. +Each optional catch-all pattern should be independent and not nested within another catch-all pattern. + +```md alert +# Catch-all routes must be placed at the end of the URL pattern to ensure proper route matching. +``` + +### Routes Validation Table + +| Route Pattern | Example URl | valid | +|:------------------------------------------------------|:-------------------------------------------------------|---------:| +| `/users/posts` | `/users/posts` | valid | +| `/products/[category]` | `/products/electronics` | valid | | | +| `/users/[username]/posts/[id]` | `/users/john/posts/5` | valid | +| `/users/[...username]/posts` | `/users/john/posts` | invalid | +| | `/users/john/doe/posts` | invalid | +| `/users/[...username]` | `/users/john/` | valid | +| | `/users/john/doe` | valid | +| `/products/[category]/[...subcategories]` | `/products/electronics/laptops` | valid | +| | `/products/electronics/laptops/lenovo` | valid | +| `/products/[category]/[[...subcategories]]` | `/products/electronics` | valid | +| | `/products/electronics/laptops` | valid | +| | `/products/electronics/laptops/lenovo` | valid | +| | `/products/electronics/laptops/lenovo/thinkpad` | valid | +| `/products/[category]/[...subcategories]/[...items]` | `/products/electronics/laptops` | invalid | +| | `/products/electronics/laptops/lenovo` | invalid | +| | `/products/electronics/laptops/lenovo/thinkpad` | invalid | diff --git a/docs/pages/metadata.md b/docs/pages/metadata.md new file mode 100644 index 000000000..76842a0d6 --- /dev/null +++ b/docs/pages/metadata.md @@ -0,0 +1,45 @@ +```python exec + +import reflex as rx + +meta_data = ( +""" +@rx.page( + title='My Beautiful App', + description='A beautiful app built with Reflex', + image='/splash.png', + meta=meta, +) +def index(): + return rx.text('A Beautiful App') + +@rx.page(title='About Page') +def about(): + return rx.text('About Page') + + +meta = [ + {'name': 'theme_color', 'content': '#FFFFFF'}, + {'char_set': 'UTF-8'}, + {'property': 'og:url', 'content': 'url'}, +] + +app = rx.App() +""" + +) + +``` + +# Page Metadata + +You can add page metadata such as: + +- The title to be shown in the browser tab +- The description as shown in search results +- The preview image to be shown when the page is shared on social media +- Any additional metadata + +```python +{meta_data} +``` diff --git a/docs/pages/routes.md b/docs/pages/routes.md new file mode 100644 index 000000000..19277ac0a --- /dev/null +++ b/docs/pages/routes.md @@ -0,0 +1,178 @@ +```python exec +import reflex as rx +from pcweb import constants, styles + + + +route = ( +""" +def index(): + return rx.text('Root Page') + +def about(): + return rx.text('About Page') + + +def custom(): + return rx.text('Custom Route') + +app = rx.App() + +app.add_page(index) +app.add_page(about) +app.add_page(custom, route="/custom-route") +""" +) + +nested_routes = ( +""" +@rx.page(route='/nested/page') +def nested_page(): + return rx.text('Nested Page') + +app = rx.App() +""" + +) + + +routes_get_query_params = ( +""" +class State(rx.State): + @rx.var + def user_post(self) -> str: + args = self.router.page.params + username = args.get('username') + post_id = args.get('id') + return f'Posts by {username}: Post {post_id}' + +@rx.page(route='/user/[username]/posts/[id]') +def post(): + return rx.center( + rx.text(State.user_post) + ) + + +app = rx.App() +""" +) + + +page_decorator = ( +""" +@rx.page(route='/', title='My Beautiful App') +def index(): + return rx.text('A Beautiful App') +""" +) + +current_page_link = ( +""" +class State(rx.State): + + @rx.var + def current_url(self) -> str: + return self.router.page.full_raw_path +""" +) + + +current_page_info = ( +""" +class State(rx.State): + def some_method(self): + current_page_route = self.router.page.path + current_page_url = self.router.page.raw_path + # ... Your logic here ... + +""" +) + +client_ip = ( +""" +class State(rx.State): + def some_method(self): + client_ip = self.router.session.client_ip + # ... Your logic here ... + +""" +) +``` + +# Pages + +Pages in Reflex allow you to define components for different URLs. This section covers creating pages, handling URL +arguments, accessing query parameters, managing page metadata, and handling page load events. + +## Adding a Page + +You can create a page by defining a function that returns a component. +By default, the function name will be used as the route, but you can also specify a route. + +```python +{route.strip()} +``` + +In this example we create three pages: + +- `index` - The root route, available at `/` +- `about` - available at `/about` +- `/custom` - available at `/custom-route` + +## Page Decorator + +You can also use the `@rx.page` decorator to add a page. + +```python +{page_decorator} +``` + +This is equivalent to calling `app.add_page` with the same arguments. + +```md alert +# Index is a special exception where it is available at both `/` and `/index`. All other pages are only available at their specified route. +``` + +## Nested Routes + +Pages can also have nested routes. + +```python +{nested_routes} +``` + +This component will be available at `/nested/page`. + +## Getting the Current Page Link + +The `router.page.path` attribute allows you to obtain the path of the current page from the router data, +for dynamic pages this will contain the slug rather than the actual value used to load the page. + +To get the actual URL displayed in the browser, use `router.page.raw_path`. This +will contain all query parameters and dynamic path segments. + +```python +{current_page_info} +``` + +In the above example, `current_page_route` will contain the route pattern (e.g., `/posts/[id]`), while `current_page_url` +will contain the actual URL (e.g., `http://example.com/posts/123`). + +To get the full URL, access the same attributes with `full_` prefix. + +Example: + +```python +{current_page_link} +``` + +In this example, running on `localhost` should display `http://localhost:3000/user/hey/posts/3/` + +## Getting Client IP + +You can use the `router.session.client_ip` attribute to obtain the IP address of the client associated +with the current state. + +```python +{client_ip} +``` diff --git a/docs/recipes/checkboxes.md b/docs/recipes/checkboxes.md new file mode 100644 index 000000000..a54f76527 --- /dev/null +++ b/docs/recipes/checkboxes.md @@ -0,0 +1,67 @@ +```python exec +import reflex as rx +``` + +# Smart Checkboxes Group + +A smart checkboxes group where you can track all checked boxes, as well as place a limit on how many checks are possible. + +## Recipe + +```python eval +rx.center(rx.image(src="/gallery/smart_checkboxes.gif")) +``` + +This recipe use a `dict[str, bool]` for the checkboxes state tracking. +Additionally, the limit that prevent the user from checking more boxes than allowed with a computed var. + +```python +class CBoxeState(rx.State): + + choices: dict[str, bool] = \{k: False for k in ["Choice A", "Choice B", "Choice C"]} + _check_limit = 2 + + def check_choice(self, value, index): + self.choices[index] = value + + @rx.var + def choice_limit(self): + return sum(self.choices.values()) >= self._check_limit + + @rx.var + def checked_choices(self): + choices = [l for l, v in self.choices.items() if v] + return " / ".join(choices) if choices else "None" + +import reflex as rx + + +def render_checkboxes(values, limit, handler): + return rx.vstack( + rx.foreach( + values, + lambda choice: rx.checkbox( + choice[0], + checked=choice[1], + disabled=~choice[1] & limit, + on_change=lambda val: handler(val, choice[0]), + ), + ) + ) + + +def index() -> rx.Component: + + return rx.center( + rx.vstack( + rx.text("Make your choices (2 max):"), + render_checkboxes( + CBoxeState.choices, + CBoxeState.choice_limit, + CBoxeState.check_choice, + ), + rx.text("Your choices: ", CBoxeState.checked_choices), + ), + height="100vh", + ) +``` diff --git a/docs/recipes/filtered-table.md b/docs/recipes/filtered-table.md new file mode 100644 index 000000000..ca48d63ee --- /dev/null +++ b/docs/recipes/filtered-table.md @@ -0,0 +1,98 @@ +```python exec +import reflex as rx +``` + +# Filtered Table + +## Recipe + +```python eval +rx.center(rx.image(src="/gallery/filtered_table.gif")) +``` + +This recipe uses an `rx.foreach` for the row generation with a computed var filtering the data for rows, using an input value for filter value. + +Additionally, the filter input uses a debounce that limits the update, which prevents filtered data to be calculated on every keypress. + +```python +import reflex as rx +from typing import List, Dict + +RAW_DATA = [ + \{"name": "Alice", "tags": "tag1"}, + \{"name": "Bob", "tags": "tag2"}, + \{"name": "Charlie", "tags": "tag1"}, +] +RAW_DATA_COLUMNS = ["Name", "tags"] + + +class FilteredTableState(rx.State): + filter_expr: str = "" + data: Dict[str, Dict[str, str]] = RAW_DATA + + @rx.cached_var + def filtered_data(self) -> List[Dict[str, str]]: + # Use this generated filtered data view in the `rx.foreach` of + # the table renderer of rows + # It is dependent on `filter_expr` + # This `filter_expr` is set by an rx.chakra.input + return [ + row + for row in self.data + if self.filter_expr == "" + or self.filter_expr != "" + and self.filter_expr == row["tags"] + ] + + def input_filter_on_change(self, value): + self.filter_expr = value + # for DEBUGGING + yield rx.console_log(f"Filter set to: \{self.filter_expr}") + + +def render_row(row): + return rx.chakra.tr(rx.chakra.td(row["name"]), rx.chakra.td(row["tags"])) + + +def render_rows(): + return [ + rx.foreach( + # use data filtered by `filter_expr` as update by rx.chakra.input + FilteredTableState.filtered_data, + render_row, + ) + ] + + +def render_table(): + return rx.chakra.table_container( + rx.chakra.table( + rx.chakra.thead(rx.chakra.tr(*[rx.chakra.th(column) for column in RAW_DATA_COLUMNS])), + rx.chakra.tbody(*render_rows()), + ) + ) + + +def index() -> rx.Component: + return rx.box( + rx.box( + rx.heading( + "Filter by tags:", + size="sm", + ), + rx.chakra.input( + on_change=FilteredTableState.input_filter_on_change, + value=FilteredTableState.filter_expr, + debounce_timeout=1000, + ), + ), + rx.box( + render_table(), + ), + ) + + +app = rx.App() +app.add_page(index, route="/") + +``` diff --git a/docs/recipes/navbar.md b/docs/recipes/navbar.md new file mode 100644 index 000000000..9a6e99f58 --- /dev/null +++ b/docs/recipes/navbar.md @@ -0,0 +1,155 @@ +# Navigation Bar + +A navigation bar, also known as a navbar, is a common UI element found at the top of a webpage or application. +It typically provides links or buttons to the main sections of a website or application, allowing users to easily navigate and access the different pages. + +Navigation bars are useful for web apps because they provide a consistent and intuitive way for users to navigate through the app. +Having a clear and consistent navigation structure can greatly improve the user experience by making it easy for users to find the information they need and access the different features of the app. + +## Basic Navbar + +In this recipe, we will create a navbar component that can be used to create a navigation bar for a web app. +The navbar will be a simple horizontal bar that contains a logo and a list of links to the different pages of the app. + +The navbar will be created using the `rx.hstack` component, which is used to create a horizontal layout. +Since we want the navbar to be fixed to the top of the page, we set the `position` prop to `fixed` and the `top` prop to `0px`. +We also set the `z_index` prop to `5` to make sure the navbar is always on top of the screen and above the other components on the page. + +```python exec +import reflex as rx + + +def navbar(): + return rx.hstack( + rx.hstack( + rx.image(src="/favicon.ico", width="2em"), + rx.heading("My App", font_size="2em"), + ), + rx.spacer(), + rx.chakra.menu( + rx.chakra.menu_button("Menu"), + rx.chakra.menu_list( + rx.chakra.menu_item("Item 1"), + rx.chakra.menu_divider(), + rx.chakra.menu_item("Item 2"), + rx.chakra.menu_item("Item 3"), + ), + ), + # position="fixed", + # top="0px", + background_color="lightgray", + padding="1em", + height="4em", + width="100%", + z_index="5", + ) +``` + +```python demo box +navbar() +``` + +```python +def navbar(): + return rx.hstack( + rx.hstack( + rx.image(src="/favicon.ico", width="2em"), + rx.heading("My App", font_size="2em"), + ), + rx.spacer(), + rx.chakra.menu( + rx.chakra.menu_button("Menu"), + rx.chakra.menu_list( + rx.chakra.menu_item("Item 1"), + rx.chakra.menu_divider(), + rx.chakra.menu_item("Item 2"), + rx.chakra.menu_item("Item 3"), + ), + ), + position="fixed", + top="0px", + background_color="lightgray", + padding="1em", + height="4em", + width="100%", + z_index="5", + ) +``` + +## Adding Main Content + +Now that we have a navbar, we can add some content to the page. + +We wrap both the navbar and the main content in a `rx.fragment` component so that they are rendered together as a single page. +We add some padding to the top of the main content so that it is not hidden behind the navbar. +You can adjust the amount of padding to suit your needs. + +```python demo exec +def content(): + return rx.box( + rx.heading("Welcome to My App"), + rx.text("This is the main content of the page."), + ) + +def index(): + return rx.fragment( + navbar(), + rx.container( + content(), + padding_top="6em", + max_width="60em", + ), + ) +``` + +Here is the full code for a basic navbar with main content: + +```python +import reflex as rx + +def content(): + return rx.box( + rx.heading("Welcome to My App"), + rx.text("This is the main content of the page."), + ) + +def navbar(): + return rx.hstack( + rx.hstack( + rx.image(src="/favicon.ico", width="2em"), + rx.heading("My App", font_size="2em"), + ), + rx.spacer(), + rx.chakra.menu( + rx.chakra.menu_button("Menu"), + rx.chakra.menu_list( + rx.chakra.menu_item("Item 1"), + rx.chakra.menu_divider(), + rx.chakra.menu_item("Item 2"), + rx.chakra.menu_item("Item 3"), + ), + ), + position="fixed", + top="0px", + background_color="lightgray", + padding="1em", + height="4em", + width="100%", + z_index="5", + ) + + +def index(): + return rx.fragment( + navbar(), + rx.container( + content(), + padding_top="6em", + max_width="60em", + ), + ) + + +app = rx.App() +app.add_page(index) +``` diff --git a/docs/recipes/sidebar.md b/docs/recipes/sidebar.md new file mode 100644 index 000000000..eb9d2e75e --- /dev/null +++ b/docs/recipes/sidebar.md @@ -0,0 +1,160 @@ + +# Sidebar + +Similar to a navigation bar, a sidebar is a common UI element found on the side of a webpage or application. + +## Recipe + +In this recipe, we will create a sidebar component than can help with navigation in a web app. + +In this example we want the sidebar to stick to the left side of the page, so we will use the `position="fixed"` prop to make the sidebar fixed to the left side of the page. +We use `left=0` and `top=0` props to specify the position of the sidebar, and the `z_index=5` props to make sure the sidebar stays above the other components on the page. + +```python exec +import reflex as rx +from pcweb.templates.docpage import demo_box_style + +# Custom styles for sidebar. +box_style = demo_box_style.copy() +del box_style["padding"] +del box_style["align_items"] +del box_style["justify_content"] +box_style["height"] = "500px" +box_style["position"] = "relative" + +def sidebar(): + return rx.vstack( + rx.image(src="/favicon.ico",width="3em"), + rx.heading("Sidebar", margin_bottom="1em"), + position="absolute", + height="100%", + # left="0px", + # top="0px", + z_index="5", + padding_x="2em", + padding_y="1em", + background_color="lightgray", + align_items="left", + width="250px", + ) +``` + +```python eval +rx.box( + sidebar(), + style=box_style, +) +``` + +```python +def sidebar(): + return rx.vstack( + rx.image(src="/favicon.ico",width="3em"), + rx.heading("Sidebar", margin_bottom="1em"), + position="fixed", + height="100%", + left="0px", + top="0px", + z_index="5", + padding_x="2em", + padding_y="1em", + background_color="lightgray", + align_items="left", + width="250px", + ) +``` + +## Adding Main Content + +Now that we have a sidebar, we can add some content to the main part of the page. + +We wrap both the sidebar and content in an `rx.fragment`. +We also make sure the content is aligned to the right of the sidebar by setting the `margin_left` prop to `250px` (the same as the width of the sidebar). + +```python exec + +def content(): + return rx.box( + rx.heading("Welcome to My App"), + rx.text("This is the main content of the page."), + ) + + +def index(): + return rx.fragment( + sidebar(), + rx.container( + content(), + max_width="60em", + margin_left="250px", + padding="2em" + ), + ) + +``` + +```python eval +rx.box( + index(), + style=box_style, +) +``` + +```python +def content(): + return rx.box( + rx.heading("Welcome to My App"), + rx.text("This is the main content of the page."), + ) + + +def index(): + return rx.fragment( + sidebar(), + rx.container( + content(), + max_width="60em", + margin_left="250px", + padding="2em" + ), + ) +``` + +Here is the full code for a basic sidebar with main content: + +```python +import reflex as rx + + +def content(): + return rx.box( + rx.heading("Welcome to My App"), + rx.text("This is the main content of the page."), + ) + + +def sidebar(): + return rx.vstack( + rx.image(src="/favicon.ico", width="3em"), + rx.heading("Sidebar", margin_bottom="1em"), + position="fixed", + height="100%", + left="0px", + top="0px", + z_index="5", + padding_x="2em", + padding_y="1em", + background_color="lightgray", + align_items="left", + width="250px", + ) + +def index(): + return rx.fragment( + sidebar(), + rx.container(content(), max_width="60em", margin_left="250px", padding="2em"), + ) + +app = rx.App() +app.add_page(index) +``` diff --git a/docs/state/overview.md b/docs/state/overview.md new file mode 100644 index 000000000..c8fdee329 --- /dev/null +++ b/docs/state/overview.md @@ -0,0 +1,155 @@ +```python exec +import reflex as rx +from pcweb.templates.docpage import definition +``` + +# State + +State allows us to create interactive apps that can respond to user input. +It defines the variables that can change over time, and the functions that can modify them. + +## State Basics + +You can define state by creating a class that inherits from `rx.State`: + +```python +import reflex as rx + + +class State(rx.State): + """Define your app state here.""" +``` + +A state class is made up of two parts: vars and event handlers. + +**Vars** are variables in your app that can change over time. + +**Event handlers** are functions that modify these vars in response to events. + +These are the main concepts to understand how state works in Reflex: + +```python eval +rx.chakra.responsive_grid( + definition( + "Base Var", + rx.chakra.unordered_list( + rx.chakra.list_item("Any variable in your app that can change over time."), + rx.chakra.list_item( + "Defined as a field in a ", rx.code("State"), " class" + ), + rx.chakra.list_item("Can only be modified by event handlers."), + ), + ), + definition( + "Computed Var", + rx.chakra.unordered_list( + rx.chakra.list_item("Vars that change automatically based on other vars."), + rx.chakra.list_item( + "Defined as functions using the ", + rx.code("@rx.var"), + " decorator.", + ), + rx.chakra.list_item( + "Cannot be set by event handlers, are always recomputed when the state changes." + ), + ), + ), + definition( + "Event Trigger", + rx.chakra.unordered_list( + rx.chakra.list_item( + "A user interaction that triggers an event, such as a button click." + ), + rx.chakra.list_item( + "Defined as special component props, such as ", + rx.code("on_click"), + ".", + ), + rx.chakra.list_item("Can be used to trigger event handlers."), + ), + ), + definition( + "Event Handlers", + rx.chakra.unordered_list( + rx.chakra.list_item( + "Functions that update the state in response to events." + ), + rx.chakra.list_item( + "Defined as methods in the ", rx.code("State"), " class." + ), + rx.chakra.list_item( + "Can be called by event triggers, or by other event handlers." + ), + ), + ), + margin_bottom="1em", + spacing="1em", + columns=[1, 1, 2, 2, 2], +) +``` + +## Example + +Here is a example of how to use state within a Reflex app. +Click the text to change its color. + +```python demo exec +class ExampleState(rx.State): + + # A base var for the list of colors to cycle through. + colors: list[str] = ["black", "red", "green", "blue", "purple"] + + # A base var for the index of the current color. + index: int = 0 + + def next_color(self): + """An event handler to go to the next color.""" + # Event handlers can modify the base vars. + # Here we reference the base vars `colors` and `index`. + self.index = (self.index + 1) % len(self.colors) + + @rx.var + def color(self)-> str: + """A computed var that returns the current color.""" + # Computed vars update automatically when the state changes. + return self.colors[self.index] + + +def index(): + return rx.heading( + "Welcome to Reflex!", + # Event handlers can be bound to event triggers. + on_click=ExampleState.next_color, + # State vars can be bound to component props. + color=ExampleState.color, + _hover={"cursor": "pointer"}, + ) +``` + +The base vars are `colors` and `index`. They are the only vars in the app that +may be directly modified within event handlers. + +There is a single computed var, `color`, that is a function of the base vars. It +will be computed automatically whenever the base vars change. + +The heading component links its `on_click` event to the +`ExampleState.next_color` event handler, which increments the color index. + +```md alert success +# With Reflex, you never have to write an API. +All interactions between the frontend and backend are handled through events. +``` + +## Client States + +Each user who opens your app has a unique ID and their own copy of the state. +This means that each user can interact with the app and modify the state +independently of other users. + +```md alert +Try opening an app in multiple tabs to see how the state changes independently. +``` + +All user state is stored on the server, and all event handlers are executed on +the server. Reflex uses websockets to send events to the server, and to send +state updates back to the client. diff --git a/docs/styling/custom-stylesheets.md b/docs/styling/custom-stylesheets.md new file mode 100644 index 000000000..b2363faa6 --- /dev/null +++ b/docs/styling/custom-stylesheets.md @@ -0,0 +1,77 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import assets +``` + +# Custom Stylesheets + +Reflex allows you to add custom stylesheets. Simply pass the URLs of the stylesheets to `rx.App`: + +```python +app = rx.App( + stylesheets=[ + "https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css", + ], +) +``` + +## Local Stylesheets + +You can also add local stylesheets. Just put the stylesheet under [`assets/`]({assets.upload_and_download_files.path}) and pass the path to the stylesheet to `rx.App`: + +```python +app = rx.App( + stylesheets=[ + "styles.css", # This path is relative to assets/ + ], +) +``` + +## Fonts + +You can take advantage of Reflex's support for custom stylesheets to add custom fonts to your app. + +Then you can use the font in your app by setting the `font_family` prop. + +In this example, we will use the [IBM Plex Mono]({"https://fonts.google.com/specimen/IBM+Plex+Mono"}) font from Google Fonts. + +```python demo +rx.text( + "Check out my font", + font_family="IBM Plex Mono", + font_size="1.5em", +) +``` + +## Local Fonts + +By making use of the two previous points, we can also make a stylesheet that allow you to use a font hosted on your server. + +If your font is called `MyFont.otf`, copy it in `assets/fonts`. + +Now we have the font ready, let's create the stylesheet `myfont.css`. + +```css +@font-face { + font-family: MyFont; + src: url("MyFont.otf") format("opentype"); +} + +@font-face { + font-family: MyFont; + font-weight: bold; + src: url("MyFont.otf") format("opentype"); +} +``` + +Add the reference to your new Stylesheet in your App. + +```python +app = rx.App( + stylesheets=[ + "fonts/myfont.css", # This path is relative to assets/ + ], +) +``` + +And that's it! You can now use `MyFont` like any other FontFamily to style your components. diff --git a/docs/styling/overview.md b/docs/styling/overview.md new file mode 100644 index 000000000..ff36c515a --- /dev/null +++ b/docs/styling/overview.md @@ -0,0 +1,243 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import styling, library +``` + +# Styling + +Reflex components can be styled using the full power of [CSS]({"https://www.w3schools.com/css/"}). + +There are three main ways to add style to your app and they take precedence in the following order: + +1. **Inline:** Styles applied to a single component instance. +2. **Component:** Styles applied to components of a specific type. +3. **Global:** Styles applied to all components. + +```md alert success +# Style keys can be any valid CSS property name. +To be consistent with Python standards, you can specify keys in `snake_case`. +``` + +## Global Styles + +You can pass a style dictionary to your app to apply base styles to all components. + +For example, you can set the default font family and font size for your app here just once rather than having to set it on every component. + +```python +style = { + "font_family": "Comic Sans MS", + "font_size": "16px", +} + +app = rx.App(style=style) +``` + +## Component Styles + +In your style dictionary, you can also specify default styles for specific component types or arbitrary CSS classes and IDs. + +```python +style = { + # Set the selection highlight color globally. + "::selection": { + "background_color": accent_color, + }, + # Apply global css class styles. + ".some-css-class": { + "text_decoration": "underline", + }, + # Apply global css id styles. + "#special-input": \{"width": "20vw"}, + # Apply styles to specific components. + rx.text: { + "font_family": "Comic Sans MS", + }, + rx.divider: { + "margin_bottom": "1em", + "margin_top": "0.5em", + }, + rx.heading: { + "font_weight": "500", + }, + rx.code: { + "color": "green", + }, +} + +app = rx.App(style=style) +``` + +Using style dictionaries like this, you can easily create a consistent theme for your app. + + +```md alert warning +# Watch out for underscores in class names and IDs +Reflex automatically converts `snake_case` identifiers into `camelCase` format when applying styles. To ensure consistency, it is recommended to use the dash character or camelCase identifiers in your own class names and IDs. To style third-party libraries relying on underscore class names, an external stylesheet should be used. See [custom stylesheets]({styling.custom_stylesheets.path}) for how to reference external CSS files. +``` + +## Inline Styles + +Inline styles apply to a single component instance. They are passed in as regular props to the component. + +```python demo +rx.text( + "Hello World", + background_image="linear-gradient(271.68deg, #EE756A 0.75%, #756AEE 88.52%)", + background_clip="text", + font_weight="bold", + font_size="2em", +) +``` + +Children components inherit inline styles unless they are overridden by their own inline styles. + +```python demo +rx.box( + rx.hstack( + rx.button("Default Button"), + rx.button("Red Button", color="red"), + ), + color="blue", +) +``` + +### Style Prop + +Inline styles can also be set with a `style` prop. This is useful for reusing styles betweeen multiple components. + +```python exec +text_style = { + "color": "green", + "font_family": "Comic Sans MS", + "font_size": "1.2em", + "font_weight": "bold", + "box_shadow": "rgba(240, 46, 170, 0.4) 5px 5px, rgba(240, 46, 170, 0.3) 10px 10px", +} +``` + +```python +text_style={text_style} +``` + +```python demo +rx.vstack( + rx.text("Hello", style=text_style), + rx.text("World", style=text_style), +) +``` + +```python exec +style1 = { + "color": "green", + "font_family": "Comic Sans MS", + "border_radius": "10px", + "background_color": "rgb(107,99,246)", +} +style2 = { + "color": "white", + "border": "5px solid #EE756A", + "padding": "10px", +} +``` + +```python +style1={style1} +style2={style2} +``` + +```python demo +rx.box( + "Multiple Styles", + style=[style1, style2], +) +``` + +The style dictionaries are applied in the order they are passed in. This means that styles defined later will override styles defined earlier. + + +## Theming + +As of Reflex 'v0.4.0', you can now theme your Reflex web apps. To learn more checkout the [Theme docs]({styling.theming.path}). + +The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in your rx.App. + +```python +app = rx.App( + theme=rx.theme( + appearance="light", has_background=True, radius="large", accent_color="teal" + ) +) +``` + +Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs]({library.theming.theme_panel.path}). + + + + +## Tailwind + +Reflex supports [Tailwind CSS]({"https://tailwindcss.com/"}) out of the box. To enable it, pass in a dictionary for the `tailwind` argument of your `rxconfig.py`: + +```python +import reflex as rx + + +class AppConfig(rx.Config): + pass + + +config = AppConfig( + app_name="app", + db_url="sqlite:///reflex.db", + env=rx.Env.DEV, + tailwind=\{}, +) +``` + +All Tailwind configuration options are supported. Plugins and presets are automatically wrapped in `require()`: + +```python +config = AppConfig( + app_name="app", + db_url="sqlite:///reflex.db", + env=rx.Env.DEV, + tailwind={ + "theme": { + "extend": \{}, + }, + "plugins": ["@tailwindcss/typography"], + }, +) +``` + +You can use any of the [utility classes]({"https://tailwindcss.com/docs/utility-first"}) under the `class_name` prop: + +```python demo +rx.box( + "Hello World", + class_name="text-4xl text-center text-blue-500", +) +``` + +### Disabling Tailwind + +If you want to disable Tailwind in your configuration, you can do so by setting the `tailwind` config to `None`. This can be useful if you need to temporarily turn off Tailwind for your project: + +```python +config = rx.Config(app_name="app", tailwind=None) +``` + +With this configuration, Tailwind will be disabled, and no Tailwind styles will be applied to your application. + +## Special Styles + +We support all of Chakra UI's [pseudo styles]({"https://chakra-ui.com/docs/features/style-props#pseudo"}). + +Below is an example of text that changes color when you hover over it. + +```python demo +rx.box( + rx.text("Hover Me", _hover={"color": "red"}), +) +``` diff --git a/docs/styling/responsive.md b/docs/styling/responsive.md new file mode 100644 index 000000000..5b0209f2e --- /dev/null +++ b/docs/styling/responsive.md @@ -0,0 +1,88 @@ +```python exec +import reflex as rx +``` + +# Responsive + +Reflex apps can be made responsive to look good on mobile, tablet, and desktop. + +You can pass a list of values to any style property to specify it's value on different screen sizes. + +```python demo +rx.text( + "Hello World", + color=["orange", "red", "purple", "blue", "green"], +) +``` + +The text will change color based on your screen size. If you are on desktop, try changing the size of your browser window to see the color change. + +The default breakpoints are shown below. + +```json +"sm": '30em', +"md": '48em', +"lg": '62em', +"xl": '80em', +"2xl": '96em', +``` + +## Showing Components Based on Display + +A common use case for responsive is to show different components based on the screen size. + +Reflex provides useful helper components for this. + +```python demo +rx.vstack( + rx.desktop_only( + rx.text("Desktop View"), + ), + rx.tablet_only( + rx.text("Tablet View"), + ), + rx.mobile_only( + rx.text("Mobile View"), + ), + rx.mobile_and_tablet( + rx.text("Visible on Mobile and Tablet"), + ), + rx.tablet_and_desktop( + rx.text("Visible on Desktop and Tablet"), + ), +) +``` + +## Specifying Display Breakpoints + +You can specify the breakpoints to use for the responsive components by using the `display` style property. + +```python demo +rx.vstack( + rx.text( + "Hello World", + color="green", + display=["none", "none", "none", "none", "flex"], + ), + rx.text( + "Hello World", + color="blue", + display=["none", "none", "none", "flex", "flex"], + ), + rx.text( + "Hello World", + color="red", + display=["none", "none", "flex", "flex", "flex"], + ), + rx.text( + "Hello World", + color="orange", + display=["none", "flex", "flex", "flex", "flex"], + ), + rx.text( + "Hello World", + color="yellow", + display=["flex", "flex", "flex", "flex", "flex"], + ), +) +``` diff --git a/docs/styling/theming.md b/docs/styling/theming.md new file mode 100644 index 000000000..c45a2054b --- /dev/null +++ b/docs/styling/theming.md @@ -0,0 +1,90 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import library +``` + +# Theming + +As of Reflex 'v0.4.0', you can now theme your Reflex applications. The core of our theming system is directly based on the [Radix Themes](https://radix-ui.com/docs/themes) library. This allows you to easily change the theme of your application along with providding a default light and dark theme. + +## Theme + +The `Theme` component is used to change the theme of the application. The `Theme` can be set directly in your rx.App. + +```python +app = rx.App( + theme=rx.theme( + appearance="light", has_background=True, radius="large", accent_color="teal" + ) +) +``` + +Additionally you can modify the theme of your app through using the `Theme Panel` component which can be found in the [Theme Panel docs]({library.theming.theme_panel.path}). + +## Colors + +### Color Scheme + +ON a high level components `color_scheme` inherits from the color specified in the the theme. This means that if you change the theme, the color of the component will also change. + +You can also specifiy the `color_scheme` prop. + +```python demo +rx.flex( + rx.button( + "Hello World", + color_scheme="tomato", + ), + rx.button( + "Hello World", + color_scheme="teal", + ), + spacing="2" +) +``` + +### Specific Shades of Palettes + +To access a specific shade of color from the theme, you can use the `rx.color'. When switching to light and dark themes, the color will automatically change. + +Shades can be accessed by using the color name and the shade number. The shade number ranges from 1 to 12. Additionally, they can have their alpha value set by using the `True` parameter it defaults to `False`. + +```python demo +rx.flex( + rx.button( + "Hello World", + color=rx.color("grass", 1), + background_color=rx.color("grass", 12), + border_color=f"1px solid {rx.color('grass', 1)}", + ), + rx.button( + "Hello World", + color=rx.color("grass", 1, True), + background_color=rx.color("grass", 12, True), + border_color=f"1px solid {rx.color('grass', 1, True)}", + ), + spacing="2" +) +``` + +### Regular Colors + +You can also use standard hex, rgb, and rgba colors. + +```python demo +rx.flex( + rx.button( + "Hello World", + color="white", + background_color="blue", + border_color="1px solid red", + ), + rx.button( + "Hello World", + color="#ff0000", + background_color="rgba(0, 0, 255, 0.5)", + border_color="1px solid #ff0000", + ), + spacing="2" +) +``` diff --git a/docs/substates/overview.md b/docs/substates/overview.md new file mode 100644 index 000000000..8a960a2a4 --- /dev/null +++ b/docs/substates/overview.md @@ -0,0 +1,118 @@ +```python exec +import reflex as rx +``` + +# Substates + +Substates allow you to break up your state into multiple classes to make it more manageable. This is useful as your app grows, as it allows you to think about each page as a separate entity. Substates also allow you to share common state resources, such as variables or event handlers. + +## Multiple States + +One common pattern is to create a substate for each page in your app. +This allows you to think about each page as a separate entity, and makes it easier to manage your code as your app grows. + +To create a substate, simply inherit from `rx.State` multiple times: + +```python +# index.py +import reflex as rx + +class IndexState(rx.State): + """Define your main state here.""" + data: str = "Hello World" + + +@rx.page() +def index(): + return rx.box(rx.text(IndexState.data) + +# signup.py +import reflex as rx + + +class SignupState(rx.State): + """Define your signup state here.""" + username: str = "" + password: str = "" + + def signup(self): + ... + + +@rx.page() +def signup_page(): + return rx.box( + rx.chakra.input(value=SignupState.username), + rx.chakra.input(value=SignupState.password), + ) + +# login.py +import reflex as rx + +class LoginState(rx.State): + """Define your login state here.""" + username: str = "" + password: str = "" + + def login(self): + ... + +@rx.page() +def login_page(): + return rx.box( + rx.chakra.input(value=LoginState.username), + rx.chakra.input(value=LoginState.password), + ) +``` + +Separating the states is purely a matter of organization. You can still access the state from other pages by importing the state class. + +```python +# index.py + +import reflex as rx + +from signup import SignupState + +... + +def index(): + return rx.box( + rx.text(IndexState.data), + rx.chakra.input(value=SignupState.username), + rx.chakra.input(value=SignupState.password), + ) +``` + +## State Inheritance + +A substate can also inherit from another substate other than `rx.State`, allowing you to create a hierarchy of states. + +For example, you can create a base state that defines variables and event handlers that are common to all pages in your app, such as the current logged in user. + +```python +class BaseState(rx.State): + """Define your base state here.""" + + current_user: str = "" + + def logout(self): + self.current_user = "" + + +class LoginState(BaseState): + """Define your login state here.""" + + username: str = "" + password: str = "" + + def login(self, username, password): + # authenticate + authenticate(...) + + # Set the var on the parent state. + self.current_user = username +``` + +You can access the parent state properties from a child substate, however you +cannot access the child properties from the parent state. diff --git a/docs/tutorial/adding-state.md b/docs/tutorial/adding-state.md new file mode 100644 index 000000000..b2e613e8e --- /dev/null +++ b/docs/tutorial/adding-state.md @@ -0,0 +1,207 @@ +```python exec +import os + +import reflex as rx +import openai + +from pcweb.pages.docs import events +from pcweb.pages.docs import library +from pcweb.pages.docs import state + +from docs.tutorial.tutorial_utils import ChatappState +import docs.tutorial.tutorial_style as style + +# If it's in environment, no need to hardcode (openai SDK will pick it up) +if "OPENAI_API_KEY" not in os.environ: + openai.api_key = "YOUR_OPENAI_KEY" + +``` + +# State + +Now let’s make the chat app interactive by adding state. The state is where we define all the variables that can change in the app and all the functions that can modify them. You can learn more about state in the [state docs]({state.overview.path}). + +## Defining State + +We will create a new file called `state.py` in the `chatapp` directory. Our state will keep track of the current question being asked and the chat history. We will also define an event handler `answer` which will process the current question and add the answer to the chat history. + +```python +# state.py +import reflex as rx + + +class State(rx.State): + + # The current question being asked. + question: str + + # Keep track of the chat history as a list of (question, answer) tuples. + chat_history: list[tuple[str, str]] + + def answer(self): + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, answer)) + +``` + +## Binding State to Components + +Now we can import the state in `chatapp.py` and reference it in our frontend components. We will modify the `chat` component to use the state instead of the current fixed questions and answers. + +```python exec +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(rx.text(question, style=style.question_style), text_align="right"), + rx.box(rx.text(answer, style=style.answer_style), text_align="left"), + margin_y="1em", + width="100%", + ) + + +def chat1() -> rx.Component: + return rx.box( + rx.foreach( + ChatappState.chat_history, lambda messages: qa(messages[0], messages[1]) + ) + ) + + +def action_bar1() -> rx.Component: + return rx.hstack( + rx.chakra.input( + placeholder="Ask a question", + on_change=ChatappState.set_question, + style=style.input_style, + ), + rx.button("Ask", on_click=ChatappState.answer, style=style.button_style), + ) +``` + +```python demo box +rx.container( + chat1(), + action_bar1(), +) +``` + +```python +# chatapp.py +from chatapp.state import State +... + +def chat() -> rx.Component: + return rx.box( + rx.foreach( + State.chat_history, + lambda messages: qa(messages[0], messages[1]) + ) + ) + +... + +def action_bar() -> rx.Component: + return rx.hstack( + rx.chakra.input(placeholder="Ask a question", on_change=State.set_question, style=style.input_style), + rx.button("Ask", on_click=State.answer, style=style.button_style), + ) +``` + +Normal Python `for` loops don't work for iterating over state vars because these values can change and aren't known at compile time. Instead, we use the [foreach]({library.layout.foreach.path}) component to iterate over the chat history. + +We also bind the input's `on_change` event to the `set_question` event handler, which will update the `question` state var while the user types in the input. We bind the button's `on_click` event to the `answer` event handler, which will process the question and add the answer to the chat history. The `set_question` event handler is a built-in implicitly defined event handler. Every base var has one. Learn more in the [events docs]({events.setters.path}) under the Setters section. + +## Clearing the Input + +Currently the input doesn't clear after the user clicks the button. We can fix this by binding the value of the input to `question`, with `value=State.question`, and clear it when we run the event handler for `answer`, with `self.question = ''`. + +```python exec +def action_bar2() -> rx.Component: + return rx.hstack( + rx.chakra.input( + value=ChatappState.question, + placeholder="Ask a question", + on_change=ChatappState.set_question, + style=style.input_style, + ), + rx.button("Ask", on_click=ChatappState.answer2, style=style.button_style), + ) +``` + +```python demo box +rx.container( + chat1(), + action_bar2(), +) +``` + +```python +# chatapp.py +def action_bar() -> rx.Component: + return rx.hstack( + rx.chakra.input( + value=State.question, + placeholder="Ask a question", + on_change=State.set_question, + style=style.input_style), + rx.button("Ask", on_click=State.answer, style=style.button_style), + ) + +```python +# state.py + +def answer(self): + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, answer)) + self.question = "" +``` + +## Streaming Text + +Normally state updates are sent to the frontend when an event handler returns. However, we want to stream the text from the chatbot as it is generated. We can do this by yielding from the event handler. See the [yield events docs]({events.yield_events.path}) for more info. + +```python exec +def action_bar3() -> rx.Component: + return rx.hstack( + rx.chakra.input( + value=ChatappState.question, + placeholder="Ask a question", + on_change=ChatappState.set_question, + style=style.input_style, + ), + rx.button("Ask", on_click=ChatappState.answer3, style=style.button_style), + ) +``` + +```python demo box +rx.container( + chat1(), + action_bar3(), +) +``` + +```python +# state.py +import asyncio + +... +async def answer(self): + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, "")) + + # Clear the question input. + self.question = "" + # Yield here to clear the frontend input before continuing. + yield + + for i in range(len(answer)): + # Pause to show the streaming effect. + await asyncio.sleep(0.1) + # Add one letter at a time to the output. + self.chat_history[-1] = (self.chat_history[-1][0], answer[:i + 1]) + yield +``` + +In the next section, we will finish our chatbot by adding AI! diff --git a/docs/tutorial/final-app.md b/docs/tutorial/final-app.md new file mode 100644 index 000000000..66cd26827 --- /dev/null +++ b/docs/tutorial/final-app.md @@ -0,0 +1,221 @@ +```python exec +import reflex as rx +from docs.tutorial.tutorial_utils import ChatappState +import docs.tutorial.tutorial_style as style +from pcweb.pages.docs import hosting +``` + +# Final App + +We will use OpenAI's API to give our chatbot some intelligence. + +## Using the API + +We need to modify our event handler to send a request to the API. + +```python exec +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(rx.text(question, style=style.question_style), text_align="right"), + rx.box(rx.text(answer, style=style.answer_style), text_align="left"), + margin_y="1em", + width="100%", + ) + + +def chat1() -> rx.Component: + return rx.box( + rx.foreach( + ChatappState.chat_history, lambda messages: qa(messages[0], messages[1]) + ) + ) + + +def action_bar3() -> rx.Component: + return rx.hstack( + rx.chakra.input( + value=ChatappState.question, + placeholder="Ask a question", + on_change=ChatappState.set_question, + style=style.input_style, + ), + rx.button("Ask", on_click=ChatappState.answer4, style=style.button_style), + ) +``` + +```python demo box +rx.container( + chat1(), + action_bar3(), +) +``` + +```python +# state.py +import os +from openai import OpenAI + +openai.api_key = os.environ["OPENAI_API_KEY"] + +... + +def answer(self): + # Our chatbot has some brains now! + client = OpenAI() + session = client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[ + \{"role": "user", "content": self.question} + ], + stop=None, + temperature=0.7, + stream=True, + ) + + # Add to the answer as the chatbot responds. + answer = "" + self.chat_history.append((self.question, answer)) + + # Clear the question input. + self.question = "" + # Yield here to clear the frontend input before continuing. + yield + + for item in session: + if hasattr(item.choices[0].delta, "content"): + if item.choices[0].delta.content is None: + # presence of 'None' indicates the end of the response + break + answer += item.choices[0].delta.content + self.chat_history[-1] = (self.chat_history[-1][0], answer) + yield +``` + +Finally, we have our chatbot! + +## Final Code + +We wrote all our code in three files, which you can find below. + +```python +# chatapp.py +import reflex as rx + +from chatapp import style +from chatapp.state import State + + +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(rx.text(question, text_align="right"), style=style.question_style), + rx.box(rx.text(answer, text_align="left"), style=style.answer_style), + margin_y="1em", + ) + +def chat() -> rx.Component: + return rx.box( + rx.foreach( + State.chat_history, + lambda messages: qa(messages[0], messages[1]) + ) + ) + + +def action_bar() -> rx.Component: + return rx.hstack( + rx.chakra.input( + value=State.question, + placeholder="Ask a question", + on_change=State.set_question, + style=style.input_style, + ), + rx.button("Ask", on_click=State.answer, style=style.button_style), + ) + + +def index() -> rx.Component: + return rx.container( + chat(), + action_bar(), + ) + + +app = rx.App() +app.add_page(index) +``` + +```python +# state.py +import reflex as rx +import os +import openai + + +openai.api_key = os.environ["OPENAI_API_KEY"] + +class State(rx.State): + + # The current question being asked. + question: str + + # Keep track of the chat history as a list of (question, answer) tuples. + chat_history: list[tuple[str, str]] + + def answer(self): + # Our chatbot has some brains now! + client = OpenAI() + session = client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[ + \{"role": "user", "content": self.question} + ], + stop=None, + temperature=0.7, + stream=True, + ) + + # Add to the answer as the chatbot responds. + answer = "" + self.chat_history.append((self.question, answer)) + + # Clear the question input. + self.question = "" + # Yield here to clear the frontend input before continuing. + yield + + for item in session: + if hasattr(item.choices[0].delta, "content"): + if item.choices[0].delta.content is None: + # presence of 'None' indicates the end of the response + break + answer += item.choices[0].delta.content + self.chat_history[-1] = (self.chat_history[-1][0], answer) + yield + +``` + +```python +# style.py + +# Common styles for questions and answers. +shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px" +chat_margin = "20%" +message_style = dict( + padding="1em", + border_radius="5px", + margin_y="0.5em", + box_shadow=shadow, +) + +# Set specific styles for questions and answers. +question_style = message_style | dict(bg="#F5EFFE", margin_left=chat_margin) +answer_style = message_style | dict(bg="#DEEAFD", margin_right=chat_margin) +``` + +## Next Steps + +Congratulations! You have built your first chatbot. From here, you can read through the rest of the documentations to learn about Reflex in more detail. The best way to learn is to build something, so try to build your own app using this as a starting point! + +## One More Thing + +With our hosting service, you can deploy this app with a single command within minutes. Check out our [Hosting Quick Start]({hosting.deploy_quick_start.path}). diff --git a/docs/tutorial/frontend.md b/docs/tutorial/frontend.md new file mode 100644 index 000000000..887aeceff --- /dev/null +++ b/docs/tutorial/frontend.md @@ -0,0 +1,262 @@ +```python exec +import reflex as rx +from pcweb.pages.docs import components +from pcweb.pages.docs import styling +from pcweb.pages.docs import library +import docs.tutorial.tutorial_style as style +``` + +# Basic Frontend + +Let's start with defining the frontend for our chat app. In Reflex, the frontend can be broken down into independent, reusable components. See the [components docs]({components.props.path}) for more information. + +## Display A Question And Answer + +We will modify the `index` function in `chatapp/chatapp.py` file to return a component that displays a single question and answer. + +```python demo box +rx.fragment( + rx.box( + "What is Reflex?", + # The user's question is on the right. + text_align="right", + ), + rx.box( + "A way to build web apps in pure Python!", + # The answer is on the left. + text_align="left", + ), +) +``` + +```python +# chatapp.py + +import reflex as rx + + +def index() -> rx.Component: + return rx.container( + rx.box( + "What is Reflex?", + # The user's question is on the right. + text_align="right", + ), + rx.box( + "A way to build web apps in pure Python!", + # The answer is on the left. + text_align="left", + ), + ) + + +# Add state and page to the app. +app = rx.App() +app.add_page(index) +``` + +Components can be nested inside each other to create complex layouts. Here we create a parent container that contains two boxes for the question and answer. + +We also add some basic styling to the components. Components take in keyword arguments, called [props]({components.style_props.path}), that modify the appearance and functionality of the component. We use the `text_align` prop to align the text to the left and right. + +## Reusing Components + +Now that we have a component that displays a single question and answer, we can reuse it to display multiple questions and answers. We will move the component to a separate function `question_answer` and call it from the `index` function. + +```python exec +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(question, text_align="right"), + rx.box(answer, text_align="left"), + margin_y="1em", + ) + + +qa_pairs = [ + ("What is Reflex?", "A way to build web apps in pure Python!"), + ( + "What can I make with it?", + "Anything from a simple website to a complex web app!", + ), +] + + +def chat() -> rx.Component: + qa_pairs = [ + ("What is Reflex?", "A way to build web apps in pure Python!"), + ( + "What can I make with it?", + "Anything from a simple website to a complex web app!", + ), + ] + return rx.box(*[qa(question, answer) for question, answer in qa_pairs]) +``` + +```python demo box +rx.container(chat()) +``` + +```python +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(question, text_align="right"), + rx.box(answer, text_align="left"), + margin_y="1em", + ) + + +def chat() -> rx.Component: + qa_pairs = [ + ("What is Reflex?", "A way to build web apps in pure Python!"), + ("What can I make with it?", "Anything from a simple website to a complex web app!"), + ] + return rx.box(*[qa(question, answer) for question, answer in qa_pairs]) + + +def index() -> rx.Component: + return rx.container(chat()) +``` + +## Chat Input + +Now we want a way for the user to input a question. For this, we will use the [input]({library.chakra.forms.input.path}) component to have the user add text and a [button]({library.forms.button.path}) component to submit the question. + +```python exec +def action_bar() -> rx.Component: + return rx.hstack( + rx.chakra.input(placeholder="Ask a question"), + rx.button("Ask"), + ) +``` + +```python demo box +rx.container( + chat(), + action_bar(), +) +``` + +```python +def action_bar() -> rx.Component: + return rx.hstack( + rx.chakra.input(placeholder="Ask a question"), + rx.button("Ask"), + ) + +def index() -> rx.Component: + return rx.container( + chat(), + action_bar(), + ) +``` + +## Styling + +Let's add some styling to the app. More information on styling can be found in the [styling docs]({styling.overview.path}). To keep our code clean, we will move the styling to a separate file `chatapp/style.py`. + +```python +# style.py + +# Common styles for questions and answers. +shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px" +chat_margin = "20%" +message_style = dict( + padding="1em", + border_radius="5px", + margin_y="0.5em", + box_shadow=shadow, + max_width="30em", + display="inline-block", +) + +# Set specific styles for questions and answers. +question_style = message_style | dict(margin_left=chat_margin) +answer_style = message_style | dict(margin_right=chat_margin) + +# Styles for the action bar. +input_style = dict( + border_width="1px", padding="1em", box_shadow=shadow +) +button_style = dict( + background_color="#CEFFEE", box_shadow=shadow +) +``` + +We will import the styles in `chatapp.py` and use them in the components. At this point, the app should look like this: + +```python exec +def qa4(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(rx.text(question, style=style.question_style), background_color="#F5EFFE", text_align="right"), + rx.box(rx.text(answer, style=style.answer_style), background_color="#DEEAFD", text_align="left"), + margin_y="1em", + width="100%", + ) + + +def chat4() -> rx.Component: + qa_pairs = [ + ("What is Reflex?", "A way to build web apps in pure Python!"), + ( + "What can I make with it?", + "Anything from a simple website to a complex web app!", + ), + ] + return rx.box(*[qa4(question, answer) for question, answer in qa_pairs]) + + +def action_bar4() -> rx.Component: + return rx.hstack( + rx.chakra.input(placeholder="Ask a question", style=style.input_style), + rx.button("Ask", style=style.button_style), + ) +``` + +```python demo box +rx.container( + chat4(), + action_bar4(), +) +``` + +```python +# chatapp.py +import reflex as rx + +from chatapp import style + + +def qa(question: str, answer: str) -> rx.Component: + return rx.box( + rx.box(rx.text(question, style=style.question_style), text_align="right"), + rx.box(rx.text(answer, style=style.answer_style), text_align="left"), + margin_y="1em", + ) + +def chat() -> rx.Component: + qa_pairs = [ + ("What is Reflex?", "A way to build web apps in pure Python!"), + ("What can I make with it?", "Anything from a simple website to a complex web app!"), + ] + return rx.box(*[qa(question, answer) for question, answer in qa_pairs]) + + +def action_bar() -> rx.Component: + return rx.hstack( + rx.chakra.input(placeholder="Ask a question", style=style.input_style), + rx.button("Ask", style=style.button_style), + ) + + +def index() -> rx.Component: + return rx.container( + chat(), + action_bar(), + ) + + +app = rx.App() +app.add_page(index) +``` + +The app is looking good, but it's not very useful yet! In the next section, we will add some functionality to the app. diff --git a/docs/tutorial/intro.md b/docs/tutorial/intro.md new file mode 100644 index 000000000..b754ffb23 --- /dev/null +++ b/docs/tutorial/intro.md @@ -0,0 +1,18 @@ +```python exec +from pcweb.constants import CHAT_APP_URL +``` + +# Interactive Tutorial: AI Chat App + +This tutorial will walk you through building an AI chat app with Reflex. This app is fairly complex, but don't worry - we'll break it down into small steps. + +You can find the full source code for this app [here]({CHAT_APP_URL}). + +## What You'll Learn + +In this tutorial you'll learn how to: + +1. Install `reflex` and set up your development environment. +2. Create components to define and style your UI. +3. Use state to add interactivity to your app. +4. Deploy your app to share with others. diff --git a/docs/tutorial/setup.md b/docs/tutorial/setup.md new file mode 100644 index 000000000..148c80dec --- /dev/null +++ b/docs/tutorial/setup.md @@ -0,0 +1,55 @@ +```python exec +from pcweb import constants +import reflex as rx +``` + +# Setting up Your Project + +We will start by creating a new project and setting up our development environment. First, create a new directory for your project and navigate to it. + +```bash +~ $ mkdir chatapp +~ $ cd chatapp +``` + +Next, we will create a virtual environment for our project. This is optional, but recommended. In this example, we will use [venv]({constants.VENV_URL}) to create our virtual environment. + +```bash +chatapp $ python3 -m venv venv +$ source venv/bin/activate +``` + +Now, we will install Reflex and create a new project. This will create a new directory structure in our project directory. + +```bash +chatapp $ pip install reflex +chatapp $ reflex init +────────────────────────────────── Initializing chatapp ─────────────────────────────────── +Success: Initialized chatapp +chatapp $ ls +assets chatapp rxconfig.py venv +``` + +```python eval +rx.box(height="20px") +``` + +You can run the template app to make sure everything is working. + +```bash +chatapp $ reflex run +─────────────────────────────────── Starting Reflex App ─────────────────────────────────── +Compiling: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 1/1 0:00:00 +─────────────────────────────────────── App Running ─────────────────────────────────────── +App running at: http://localhost:3000 +``` + +```python eval +rx.box(height="20px") +``` + +You should see your app running at [http://localhost:3000]({"http://localhost:3000"}). + +Reflex also starts the backend server which handles all the state management and communication with the frontend. You can test the backend server is running by navigating to [http://localhost:8000/ping]({"http://localhost:8000/ping"}). + +Now that we have our project set up, in the next section we will start building our app! diff --git a/docs/tutorial/tutorial_style.py b/docs/tutorial/tutorial_style.py new file mode 100644 index 000000000..34084d0d8 --- /dev/null +++ b/docs/tutorial/tutorial_style.py @@ -0,0 +1,19 @@ +# Common styles for questions and answers. +shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px" +chat_margin = "20%" +message_style = dict( + padding="1em", + border_radius="5px", + margin_y="0.5em", + box_shadow=shadow, + max_width="30em", + display="inline-block", +) + +# Set specific styles for questions and answers. +question_style = message_style | dict(bg="#F5EFFE", margin_left=chat_margin) +answer_style = message_style | dict(bg="#DEEAFD", margin_right=chat_margin) + +# Styles for the action bar. +input_style = dict(border_width="1px", padding="1em", box_shadow=shadow) +button_style = dict(bg="#CEFFEE", box_shadow=shadow) diff --git a/docs/tutorial/tutorial_utils.py b/docs/tutorial/tutorial_utils.py new file mode 100644 index 000000000..08723390d --- /dev/null +++ b/docs/tutorial/tutorial_utils.py @@ -0,0 +1,65 @@ +import openai + +import reflex as rx + + +class ChatappState(rx.State): + # The current question being asked. + question: str + + # Keep track of the chat history as a list of (question, answer) tuples. + chat_history: list[tuple[str, str]] + + def answer(self): + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, answer)) + + def answer2(self): + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, answer)) + # Clear the question input. + self.question = "" + + async def answer3(self): + import asyncio + + # Our chatbot is not very smart right now... + answer = "I don't know!" + self.chat_history.append((self.question, "")) + + # Clear the question input. + self.question = "" + # Yield here to clear the frontend input before continuing. + yield + + for i in range(len(answer)): + await asyncio.sleep(0.1) + self.chat_history[-1] = (self.chat_history[-1][0], answer[: i + 1]) + yield + + def answer4(self): + # Our chatbot has some brains now! + session = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": self.question}], + stop=None, + temperature=0.7, + stream=True, + ) + + # Add to the answer as the chatbot responds. + answer = "" + self.chat_history.append((self.question, answer)) + + # Clear the question input. + self.question = "" + # Yield here to clear the frontend input before continuing. + yield + + for item in session: + if hasattr(item.choices[0].delta, "content"): + answer += item.choices[0].delta.content + self.chat_history[-1] = (self.chat_history[-1][0], answer) + yield diff --git a/docs/ui/overview.md b/docs/ui/overview.md new file mode 100644 index 000000000..63b781627 --- /dev/null +++ b/docs/ui/overview.md @@ -0,0 +1,87 @@ +```python exec +from pcweb.pages.docs import components +from pcweb.pages.docs.library import library +import reflex as rx +``` + +# UI Overview + +Components are the building blocks for your app's user interface (UI). They are the visual elements that make up your app, like buttons, text, and images. + +## Component Basics + +Components are made up of children and props. + +```md definition +# Children +* Text or other Reflex components nested inside a component. +* Passed as **positional arguments**. + +# Props +* Attributes that affect the behavior and appearance of a component. +* Passed as **keyword arguments**. +``` + +Let's take a look at the `rx.text` component. + +```python demo +rx.text('Hello World!', color='blue', font_size="1.5em") +``` + +Here `"Hello World!"` is the child text to display, while `color` and `font_size` are props that modify the appearance of the text. + +```md alert success +Regular Python data types can be passed in as children to components. +This is useful for passing in text, numbers, and other simple data types. +``` + +## Another Example + +Now let's take a look at a more complex component, which has other components nested inside it. The `rx.hstack` component is a container that arranges its children horizontally. + +```python demo +rx.hstack( + # Static 50% progress + rx.chakra.circular_progress( + rx.chakra.circular_progress_label("50", color="green"), + value=50, + ), + # "Spinning" progress + rx.chakra.circular_progress( + rx.chakra.circular_progress_label("∞", color="rgb(107,99,246)"), + is_indeterminate=True, + ), +) +``` + +Some props are specific to a component. For example, the `value` prop of the `rx.chakra.circular_progress` component controls the progress bar's value. + +Styling props like `color` are shared across many components. + +```md alert info +# You can find all the props for a component by checking its documentation page in the [component library]({library.path}). +``` + +## Pages + +Reflex apps are organized into pages. Pages link a specific URL route to a component. + +You can create a page by defining a function that returns a component. By default, the function name will be used as the path, but you can also specify a path explicitly. + +```python +def index(): + return rx.text('Root Page') + + +def about(): + return rx.text('About Page') + + +app = rx.App() +app.add_page(index, route="/") +app.add_page(about, route="/about") +``` + +In this example we add a page called `index` at the root route. +If you `reflex run` the app, you will see the `index` page at `http://localhost:3000`. +Similarly, the `about` page will be available at `http://localhost:3000/about`. diff --git a/docs/utility_methods/other_methods.md b/docs/utility_methods/other_methods.md new file mode 100644 index 000000000..fab7dee2c --- /dev/null +++ b/docs/utility_methods/other_methods.md @@ -0,0 +1,14 @@ +# Other Methods + +* `reset`: set all Vars to their default value for the given state (including substates). +* `get_value`: returns the value of a Var **without tracking changes to it**. This is useful + for serialization where the tracking wrapper is considered unserializable. +* `dict`: returns all state Vars (and substates) as a dictionary. This is + used internally when a page is first loaded and needs to be "hydrated" and + sent to the client. + +## Special Attributes + +* `dirty_vars`: a set of all Var names that have been modified since the last + time the state was sent to the client. This is used internally to determine + which Vars need to be sent to the client after processing an event. diff --git a/docs/utility_methods/router_attributes.md b/docs/utility_methods/router_attributes.md new file mode 100644 index 000000000..9e370c935 --- /dev/null +++ b/docs/utility_methods/router_attributes.md @@ -0,0 +1,59 @@ +```python exec +import reflex as rx +``` + +# State Utility Methods + +The state object has several methods and attributes that return information +about the current page, session, or state. + +## Router Attributes + +The `self.router` attribute has several sub-attributes that provide various information: + +* `router.page`: data about the current page and route + * `host`: The hostname and port serving the current page (frontend). + * `path`: The path of the current page (for dynamic pages, this will contain the slug) + * `raw_path`: The path of the page displayed in the browser (including params and dynamic values) + * `full_path`: `path` with `host` prefixed + * `full_raw_path`: `raw_path` with `host` prefixed + * `params`: Dictionary of query params associated with the request + +* `router.session`: data about the current session + * `client_token`: UUID associated with the current tab's token. Each tab has a unique token. + * `session_id`: The ID associated with the client's websocket connection. Each tab has a unique session ID. + * `client_ip`: The IP address of the client. Many users may share the same IP address. + +* `router.headers`: a selection of common headers associated with the websocket + connection. These values can only change when the websocket is re-established + (for example, during page refresh). All other headers are available in the + dictionary `self.router_data.headers`. + * `host`: The hostname and port serving the websocket (backend). + +### Example Values on this Page + +```python demo exec alignItems=start +class RouterState(rx.State): + pass + + +def router_values(): + return rx.chakra.table( + headers=["Name", "Value"], + rows=[ + [rx.text("router.page.host"), rx.code(RouterState.router.page.host)], + [rx.text("router.page.path"), rx.code(RouterState.router.page.path)], + [rx.text("router.page.raw_path"), rx.code(RouterState.router.page.raw_path)], + [rx.text("router.page.full_path"), rx.code(RouterState.router.page.full_path)], + [rx.text("router.page.full_raw_path"), rx.code(RouterState.router.page.full_raw_path)], + [rx.text("router.page.params"), rx.code(RouterState.router.page.params.to_string())], + [rx.text("router.session.client_token"), rx.code(RouterState.router.session.client_token)], + [rx.text("router.session.session_id"), rx.code(RouterState.router.session.session_id)], + [rx.text("router.session.client_ip"), rx.code(RouterState.router.session.client_ip)], + [rx.text("router.headers.host"), rx.code(RouterState.router.headers.host)], + [rx.text("router.headers.user_agent"), rx.code(RouterState.router.headers.user_agent)], + [rx.text("router.headers.to_string()"), rx.code(RouterState.router.headers.to_string())], + ], + overflow_x="auto", + ) +``` diff --git a/docs/vars/base_vars.md b/docs/vars/base_vars.md new file mode 100644 index 000000000..977483134 --- /dev/null +++ b/docs/vars/base_vars.md @@ -0,0 +1,128 @@ +```python exec +import random +import time + +import reflex as rx + +from pcweb.pages.docs import vars +``` + +# Base Vars + +Vars are any fields in your app that may change over time. A Var is directly +rendered into the frontend of the app. + +Base vars are defined as fields in your State class. + +They can have a preset default value. If you don't provide a default value, you +must provide a type annotation. + +```md alert warning +# State Vars should provide type annotations. +Reflex relies on type annotations to determine the type of state vars during the compilation process. +``` + +```python demo exec +class TickerState(rx.State): + ticker: str ="AAPL" + price: str = "$150" + + +def ticker_example(): + return rx.chakra.stat_group( + rx.chakra.stat( + rx.chakra.stat_label(TickerState.ticker), + rx.chakra.stat_number(TickerState.price), + rx.chakra.stat_help_text( + rx.chakra.stat_arrow(type_="increase"), + "4%", + ), + ), + ) +``` + +In this example `ticker` and `price` are base vars in the app, which can be modified at runtime. + +```md alert warning +# Vars must be JSON serializable. +Vars are used to communicate between the frontend and backend. They must be primitive Python types, Plotly figures, Pandas dataframes, or [a custom defined type]({vars.custom_vars.path}). +``` + +## Backend-only Vars + +Any Var in a state class that starts with an underscore is considered backend +only and will not be syncronized with the frontend. Data associated with a +specific session that is not directly rendered on the frontend should be stored +in a backend-only var to reduce network traffic and improve performance. + +They have the advantage that they don't need to be JSON serializable, however +they must still be cloudpickle-able to be used with redis in prod mode. They are +not directly renderable on the frontend, and may be used to store sensitive +values that should not be sent to the client. + +For example, a backend-only var is used to store a large data structure which is +then paged to the frontend using cached vars. + +```python demo exec +import numpy as np + + +class BackendVarState(rx.State): + _backend: np.ndarray = np.array([random.randint(0, 100) for _ in range(100)]) + offset: int = 0 + limit: int = 10 + + @rx.cached_var + def page(self) -> list[int]: + return [ + int(x) # explicit cast to int + for x in self._backend[self.offset : self.offset + self.limit] + ] + + @rx.cached_var + def page_number(self) -> int: + return (self.offset // self.limit) + 1 + (1 if self.offset % self.limit else 0) + + @rx.cached_var + def total_pages(self) -> int: + return len(self._backend) // self.limit + (1 if len(self._backend) % self.limit else 0) + + def prev_page(self): + self.offset = max(self.offset - self.limit, 0) + + def next_page(self): + if self.offset + self.limit < len(self._backend): + self.offset += self.limit + + def generate_more(self): + self._backend = np.append(self._backend, [random.randint(0, 100) for _ in range(random.randint(0, 100))]) + + +def backend_var_example(): + return rx.vstack( + rx.hstack( + rx.button( + "Prev", + on_click=BackendVarState.prev_page, + ), + rx.text(f"Page {BackendVarState.page_number} / {BackendVarState.total_pages}"), + rx.button( + "Next", + on_click=BackendVarState.next_page, + ), + rx.text("Page Size"), + rx.chakra.number_input( + width="5em", + value=BackendVarState.limit, + on_change=BackendVarState.set_limit, + ), + rx.button("Generate More", on_click=BackendVarState.generate_more), + ), + rx.chakra.list( + rx.foreach( + BackendVarState.page, + lambda x, ix: rx.text(f"_backend[{ix + BackendVarState.offset}] = {x}"), + ), + ), + ) +``` diff --git a/docs/vars/computed_vars.md b/docs/vars/computed_vars.md new file mode 100644 index 000000000..5b55c0c29 --- /dev/null +++ b/docs/vars/computed_vars.md @@ -0,0 +1,87 @@ +```python exec +import random +import time + +import reflex as rx +``` + +# Computed Vars + +Computed vars have values derived from other properties on the backend. They are +defined as methods in your State class with the `@rx.var` decorator. A computed +var is recomputed whenever an event is processed against the state. + +Try typing in the input box and clicking out. + +```python demo exec +class UppercaseState(rx.State): + text: str = "hello" + + @rx.var + def upper_text(self) -> str: + # This will be recomputed whenever `text` changes. + return self.text.upper() + + +def uppercase_example(): + return rx.vstack( + rx.heading(UppercaseState.upper_text), + rx.chakra.input(on_blur=UppercaseState.set_text, placeholder="Type here..."), + ) +``` + +Here, `upper_text` is a computed var that always holds the upper case version of `text`. + +We recommend always using type annotations for computed vars. + +## Cached Vars + +A cached var, decorated as `@rx.cached_var` is a special type of computed var +that is only recomputed when the other state vars it depends on change. This is +useful for expensive computations, but in some cases it may not update when you +expect it to. + +```python demo exec +class CachedVarState(rx.State): + counter_a: int = 0 + counter_b: int = 0 + + @rx.var + def last_touch_time(self) -> str: + # This is updated anytime the state is updated. + return time.strftime("%H:%M:%S") + + def increment_a(self): + self.counter_a += 1 + + @rx.cached_var + def last_counter_a_update(self) -> str: + # This is updated only when `counter_a` changes. + return f"{self.counter_a} at {time.strftime('%H:%M:%S')}" + + def increment_b(self): + self.counter_b += 1 + + @rx.cached_var + def last_counter_b_update(self) -> str: + # This is updated only when `counter_b` changes. + return f"{self.counter_b} at {time.strftime('%H:%M:%S')}" + + +def cached_var_example(): + return rx.vstack( + rx.text(f"State touched at: {CachedVarState.last_touch_time}"), + rx.text(f"Counter A: {CachedVarState.last_counter_a_update}"), + rx.text(f"Counter B: {CachedVarState.last_counter_b_update}"), + rx.hstack( + rx.button("Increment A", on_click=CachedVarState.increment_a), + rx.button("Increment B", on_click=CachedVarState.increment_b), + ), + ) +``` + +In this example `last_touch_time` is a normal computed var, which updates any +time the state is modified. `last_counter_a_update` is a computed var that only +depends on `counter_a`, so it only gets recomputed when `counter_a` has changes. +Similarly `last_counter_b_update` only depends on `counter_b`, and thus is +updated only when `counter_b` changes. diff --git a/docs/vars/custom_vars.md b/docs/vars/custom_vars.md new file mode 100644 index 000000000..c57e81abb --- /dev/null +++ b/docs/vars/custom_vars.md @@ -0,0 +1,41 @@ +```python exec +import reflex as rx + +from pcweb.pages.docs import vars +``` + +# Custom Vars + +As mentioned in the [vars page]({vars.base_vars.path}), Reflex vars must be JSON serializable. + +This means we can support any Python primitive types, as well as lists, dicts, and tuples. However, you can also create more complex var types by inheriting from `rx.Base`. + +## Defining a Type + +In this example, we will create a custom var type for storing translations. + +Once defined, we can use it as a state var, and reference it from within a component. + +```python demo exec +import googletrans + +class Translation(rx.Base): + original_text: str + translated_text: str + +class TranslationState(rx.State): + input_text: str = "Hola Mundo" + current_translation: Translation = Translation(original_text="", translated_text="") + + def translate(self): + text = googletrans.Translator().translate(self.input_text, dest="en").text + self.current_translation = Translation(original_text=self.input_text, translated_text=text) + + +def translation_example(): + return rx.vstack( + rx.chakra.input(on_blur=TranslationState.set_input_text, default_value=TranslationState.input_text, placeholder="Text to translate..."), + rx.button("Translate", on_click=TranslationState.translate), + rx.text(TranslationState.current_translation.translated_text), + ) +``` diff --git a/docs/vars/var-operations.md b/docs/vars/var-operations.md new file mode 100644 index 000000000..c90297f43 --- /dev/null +++ b/docs/vars/var-operations.md @@ -0,0 +1,447 @@ +```python exec +import random +import time + +import numpy as np + +import reflex as rx + +from pcweb.templates.docpage import docpage +``` + +# Var Operations + +Var operations transform the placeholder representation of the value on the +frontend and provide a way to perform basic operations on the Var without having +to define a computed var. + +Within your frontend components, you cannot use arbitrary Python functions on +the state vars. For example, the following code will **not work.** + +```python +class State(rx.State): + number: int + +def index(): + # This will be compiled before runtime, before `State.number` has a known value. + # Since `float` is not a valid var operation, this will throw an error. + rx.text(float(State.number)) +``` + +This is because we compile the frontend to Javascript, but the value of `State.number` +is only known at runtime. + +In this example below we use a var operation to concatenate a `string` with a `var`, meaning we do not have to do in within state as a computed var. + +```python demo exec +coins = ["BTC", "ETH", "LTC", "DOGE"] + +class VarSelectState(rx.State): + selected: str = "DOGE" + +def var_operations_example(): + return rx.vstack( + # Using a var operation to concatenate a string with a var. + rx.heading("I just bought a bunch of " + VarSelectState.selected), + # Using an f-string to interpolate a var. + rx.text(f"{VarSelectState.selected} is going to the moon!"), + rx.select( + coins, + value=VarSelectState.selected, + on_change=VarSelectState.set_selected, + ) + ) +``` + +```md alert success +# Vars support many common operations. +They can be used for arithmetic, string concatenation, inequalities, indexing, and more. See the [full list of supported operations](/docs/api-reference/var). +``` + +## Supported Operations + +Var operations allow us to change vars on the front-end without having to create more computed vars on the back-end in the state. + +Some simple examples are the `==` var operator, which is used to check if two vars are equal and the `to_string()` var operator, which is used to convert a var to a string. + +```python demo exec + +fruits = ["Apple", "Banana", "Orange", "Mango"] + +class EqualsState(rx.State): + selected: str = "Apple" + favorite: str = "Banana" + + +def var_equals_example(): + return rx.vstack( + rx.text(EqualsState.favorite.to_string() + "is my favorite fruit!"), + rx.select( + fruits, + value=EqualsState.selected, + on_change=EqualsState.set_selected, + ), + rx.cond( + EqualsState.selected == EqualsState.favorite, + rx.text("The selected fruit is equal to the favorite fruit!"), + rx.text("The selected fruit is not equal to the favorite fruit."), + ), + ) + +``` + +### Negate, Absolute and Length + +The `-` operator is used to get the negative version of the var. The `abs()` operator is used to get the absolute value of the var. The `.length()` operator is used to get the length of a list var. + +```python demo exec +import random + +class OperState(rx.State): + number: int + numbers_seen: list = [] + def update(self): + self.number = random.randint(-100, 100) + self.numbers_seen.append(self.number) + +def var_operation_example(): + return rx.vstack( + rx.chakra.heading(f"The number: {OperState.number}", size="md"), + rx.hstack( + rx.text("Negated:", rx.chakra.badge(-OperState.number, variant="subtle", color_scheme="green")), + rx.text(f"Absolute:", rx.chakra.badge(abs(OperState.number), variant="subtle", color_scheme="blue")), + rx.text(f"Numbers seen:", rx.chakra.badge(OperState.numbers_seen.length(), variant="subtle", color_scheme="red")), + ), + rx.button("Update", on_click=OperState.update), + ) +``` + +### Comparisons and Mathematical Operators + +All of the comparison operators are used as expected in python. These include `==`, `!=`, `>`, `>=`, `<`, `<=`. + +There are operators to add two vars `+`, subtract two vars `-`, multiply two vars `*` and raise a var to a power `pow()`. + +```python demo exec +import random + +class CompState(rx.State): + number_1: int + number_2: int + + def update(self): + self.number_1 = random.randint(-10, 10) + self.number_2 = random.randint(-10, 10) + +def var_comparison_example(): + + return rx.vstack( + rx.chakra.table_container( + rx.chakra.table( + headers=["Integer 1", "Integer 2", "Operation", "Outcome"], + rows=[ + (CompState.number_1, CompState.number_2, "Int 1 == Int 2", f"{CompState.number_1 == CompState.number_2}"), + (CompState.number_1, CompState.number_2, "Int 1 != Int 2", f"{CompState.number_1 != CompState.number_2}"), + (CompState.number_1, CompState.number_2, "Int 1 > Int 2", f"{CompState.number_1 > CompState.number_2}"), + (CompState.number_1, CompState.number_2, "Int 1 >= Int 2", f"{CompState.number_1 >= CompState.number_2}"), + (CompState.number_1, CompState.number_2, "Int 1 < Int 2 ", f"{CompState.number_1 < CompState.number_2}"), + (CompState.number_1, CompState.number_2, "Int 1 <= Int 2", f"{CompState.number_1 <= CompState.number_2}"), + (CompState.number_1, CompState.number_2, "Int 1 + Int 2", f"{CompState.number_1 + CompState.number_2}"), + (CompState.number_1, CompState.number_2, "Int 1 - Int 2", f"{CompState.number_1 - CompState.number_2}"), + (CompState.number_1, CompState.number_2, "Int 1 * Int 2", f"{CompState.number_1 * CompState.number_2}"), + (CompState.number_1, CompState.number_2, "pow(Int 1, Int2)", f"{pow(CompState.number_1, CompState.number_2)}"), + ], + variant="striped", + color_scheme="teal", + ), + ), + rx.button("Update", on_click=CompState.update), + ) +``` + +### True Division, Floor Division and Remainder + +The operator `/` represents true division. The operator `//` represents floor division. The operator `%` represents the remainder of the division. + +```python demo exec +import random + +class DivState(rx.State): + number_1: float = 3.5 + number_2: float = 1.4 + + def update(self): + self.number_1 = round(random.uniform(5.1, 9.9), 2) + self.number_2 = round(random.uniform(0.1, 4.9), 2) + +def var_div_example(): + return rx.vstack( + rx.chakra.table_container( + rx.chakra.table( + headers=["Integer 1", "Integer 2", "Operation", "Outcome"], + rows=[ + (DivState.number_1, DivState.number_2, "Int 1 / Int 2", f"{DivState.number_1 / DivState.number_2}"), + (DivState.number_1, DivState.number_2, "Int 1 // Int 2", f"{DivState.number_1 // DivState.number_2}"), + (DivState.number_1, DivState.number_2, "Int 1 % Int 2", f"{DivState.number_1 % DivState.number_2}"), + ], + variant="striped", + color_scheme="red", + ), + ), + rx.button("Update", on_click=DivState.update), + ) +``` + +### And, Or and Not + +In Reflex the `&` operator represents the logical AND when used in the front end. This means that it returns true only when both conditions are true simultaneously. +The `|` operator represents the logical OR when used in the front end. This means that it returns true when either one or both conditions are true. +The `~` operator is used to invert a var. It is used on a var of type `bool` and is equivalent to the `not` operator. + +```python demo exec +import random + +class LogicState(rx.State): + var_1: bool = True + var_2: bool = True + + def update(self): + self.var_1 = random.choice([True, False]) + self.var_2 = random.choice([True, False]) + +def var_logical_example(): + return rx.vstack( + rx.chakra.table_container( + rx.chakra.table( + headers=["Var 1", "Var 2", "Operation", "Outcome"], + rows=[ + (f"{LogicState.var_1}", f"{LogicState.var_2}", "Logical AND (&)", f"{LogicState.var_1 & LogicState.var_2}"), + (f"{LogicState.var_1}", f"{LogicState.var_2}", "Logical OR (|)", f"{LogicState.var_1 | LogicState.var_2}"), + (f"{LogicState.var_1}", f"{LogicState.var_2}", "The invert of Var 1 (~)", f"{~LogicState.var_1}"), + ], + variant="striped", + color_scheme="green", + ), + ), + rx.button("Update", on_click=LogicState.update), + ) +``` + +### Contains, Reverse and Join + +The 'in' operator is not supported for Var types, we must use the `Var.contains()` instead. When we use `contains`, the var must be of type: `dict`, `list`, `tuple` or `str`. +`contains` checks if a var contains the object that we pass to it as an argument. + +We use the `reverse` operation to reverse a list var. The var must be of type `list`. + +Finally we use the `join` operation to join a list var into a string. + +```python demo exec +class ListsState(rx.State): + list_1: list = [1, 2, 3, 4, 6] + list_2: list = [7, 8, 9, 10] + list_3: list = ["p","y","t","h","o","n"] + +def var_list_example(): + return rx.hstack( + rx.vstack( + rx.chakra.heading(f"List 1: {ListsState.list_1}", size="md"), + rx.text(f"List 1 Contains 3: {ListsState.list_1.contains(3)}"), + ), + rx.vstack( + rx.chakra.heading(f"List 2: {ListsState.list_2}", size="md"), + rx.text(f"Reverse List 2: {ListsState.list_2.reverse()}"), + ), + rx.vstack( + rx.chakra.heading(f"List 3: {ListsState.list_3}", size="md"), + rx.text(f"List 3 Joins: {ListsState.list_3.join()}"), + ), + ) +``` + +### Lower, Upper, Split + +The `lower` operator converts a string var to lowercase. The `upper` operator converts a string var to uppercase. The `split` operator splits a string var into a list. + +```python demo exec +class StringState(rx.State): + string_1: str = "PYTHON is FUN" + string_2: str = "react is hard" + + +def var_string_example(): + return rx.hstack( + rx.vstack( + rx.chakra.heading(f"List 1: {StringState.string_1}", size="md"), + rx.text(f"List 1 Lower Case: {StringState.string_1.lower()}"), + ), + rx.vstack( + rx.chakra.heading(f"List 2: {StringState.string_2}", size="md"), + rx.text(f"List 2 Upper Case: {StringState.string_2.upper()}"), + rx.text(f"Split String 2: {StringState.string_2.split()}"), + ), + ) +``` + +## Get Item (Indexing) + +Indexing is only supported for strings, lists, tuples, dicts, and dataframes. To index into a state var strict type annotations are required. + +```python +class GetItemState1(rx.State): + list_1: list = [50, 10, 20] + +def get_item_error_1(): + return rx.vstack( + rx.chakra.circular_progress(value=GetItemState1.list_1[0]) + ) +``` + +In the code above you would expect to index into the first index of the list_1 state var. In fact the code above throws the error: `Invalid var passed for prop value, expected type , got value of type typing.Any.` This is because the type of the items inside the list have not been clearly defined in the state. To fix this you change the list_1 defintion to `list_1: list[int] = [50, 10, 20]` + +```python demo exec +class GetItemState1(rx.State): + list_1: list[int] = [50, 10, 20] + +def get_item_error_1(): + return rx.vstack( + rx.chakra.circular_progress(value=GetItemState1.list_1[0]) + ) +``` + +### Using with Foreach + +Errors frequently occur when using indexing and `foreach`. + +```python +class ProjectsState(rx.State): + projects: List[dict] = [ + { + "technologies": ["Next.js", "Prisma", "Tailwind", "Google Cloud", "Docker", "MySQL"] + }, + { + "technologies": ["Python", "Flask", "Google Cloud", "Docker"] + } + ] + +def get_badge(technology: str) -> rx.Component: + return rx.chakra.badge(technology, variant="subtle", color_scheme="green") + +def project_item(project: dict): + + return rx.box( + rx.hstack( + rx.foreach(project["technologies"], get_badge) + ), + ) +``` + +The code above throws the error `TypeError: Could not foreach over var of type Any. (If you are trying to foreach over a state var, add a type annotation to the var.)` + +We must change `projects: list[dict]` => `projects: list[dict[str, list]]` because while projects is annotated, the item in project["technologies"] is not. + +```python demo exec +class ProjectsState(rx.State): + projects: list[dict[str, list]] = [ + { + "technologies": ["Next.js", "Prisma", "Tailwind", "Google Cloud", "Docker", "MySQL"] + }, + { + "technologies": ["Python", "Flask", "Google Cloud", "Docker"] + } + ] + + +def projects_example() -> rx.Component: + def get_badge(technology: str) -> rx.Component: + return rx.chakra.badge(technology, variant="subtle", color_scheme="green") + + def project_item(project: dict) -> rx.Component: + + return rx.box( + rx.hstack( + rx.foreach(project["technologies"], get_badge) + ), + ) + return rx.box(rx.foreach(ProjectsState.projects, project_item)) +``` + +The previous example had only a single type for each of the dictionaries `keys` and `values`. For complex multi-type data, you need to use a `Base var`, as shown below. + +```python demo exec +class ActressType(rx.Base): + actress_name: str + age: int + pages: list[dict[str, str]] + +class MultiDataTypeState(rx.State): + """The app state.""" + actresses: list[ActressType] = [ + ActressType( + actress_name="Ariana Grande", + age=30, + pages=[ + {"url": "arianagrande.com"}, {"url": "https://es.wikipedia.org/wiki/Ariana_Grande"} + ] + ), + ActressType( + actress_name="Gal Gadot", + age=38, + pages=[ + {"url": "http://www.galgadot.com/"}, {"url": "https://es.wikipedia.org/wiki/Gal_Gadot"} + ] + ) + ] + +def actresses_example() -> rx.Component: + def showpage(page: dict[str, str]): + return rx.vstack( + rx.text(page["url"]), + ) + + def showlist(item: ActressType): + return rx.vstack( + rx.hstack( + rx.text(item.actress_name), + rx.text(item.age), + ), + rx.foreach(item.pages, showpage), + ) + return rx.box(rx.foreach(MultiDataTypeState.actresses, showlist)) + +``` + +Setting the type of `actresses` to be `actresses: list[dict[str,str]]` would fail as it cannot be understood that the `value` for the `pages key` is actually a `list`. + +## Combine Multiple Var Operations + +You can also combine multiple var operations together, as seen in the next example. + +```python demo exec +import random + +class VarNumberState(rx.State): + number: int + + def update(self): + self.number = random.randint(0, 100) + +def var_number_example(): + return rx.vstack( + rx.chakra.heading(f"The number is {VarNumberState.number}", size="lg"), + # Var operations can be composed for more complex expressions. + rx.cond( + VarNumberState.number % 2 == 0, + rx.text("Even", color="green"), + rx.text("Odd", color="red"), + ), + rx.button("Update", on_click=VarNumberState.update), + ) +``` + +We could have made a computed var that returns the parity of `number`, but +it can be simpler just to use a var operation instead. + +Var operations may be generally chained to make compound expressions, however +some complex transformations not supported by var operations must use computed vars +to calculate the value on the backend. diff --git a/docs/wrapping-react/example.md b/docs/wrapping-react/example.md new file mode 100644 index 000000000..b7a93fdc4 --- /dev/null +++ b/docs/wrapping-react/example.md @@ -0,0 +1,378 @@ +```python exec +import reflex as rx +from typing import Any +``` + +# Complex Example + +In this more complex example we will be wrapping `reactflow` a library for building node based applications like flow charts, diagrams, graphs, etc. + +## Import + +Lets start by importing the library [reactflow](https://www.npmjs.com/package/reactflow). Lets make a seperate file called `reactflow.py` and add the following code: + +```python +from reflex.components.component import Component +from typing import Any, Dict, List, Union +from reflex.vars import Var + +class ReactFlowLib(Component): + """A component that wraps a react flow lib.""" + + library = "reactflow" + + def _get_custom_code(self) -> str: + return """import 'reactflow/dist/style.css'; + """ +``` + +Notice we also use the `_get_custom_code` method to import the css file that is needed for the styling of the library. + +## Components + +For this tutorial we will wrap three components from Reactflow: `ReactFlow`, `Background`, and `Controls`. Lets start with the `ReactFlow` component. + +Here we will define the `tag` and the `vars` that we will need to use the component. + +We will also define the `get_event_triggers` method to specify the events that the component will trigger. For this tutorial we will use `on_edges_change` and `on_connect`, but you can find all the events that the component triggers in the [reactflow docs](https://reactflow.dev/docs/api/react-flow-props/#onnodeschange). + +```python +from reflex.components.component import Component +from typing import Any, Dict, List, Union +from reflex.vars import Var + +class ReactFlowLib(Component): + ... + +class ReactFlow(ReactFlowLib): + + tag = "ReactFlow" + + nodes: Var[List[Dict[str, Any]]] + + edges: Var[List[Dict[str, Any]]] + + fit_view: Var[bool] + + nodes_draggable: Var[bool] + + nodes_connectable: Var[bool] + + nodes_focusable: Var[bool] + + def get_event_triggers(self) -> dict[str, Any]: + return { + **super().get_event_triggers(), + "on_edges_change": lambda e0: [e0], + "on_connect": lambda e0: [e0], + } +``` + +Now lets add the `Background` and `Controls` components. We will also create the components using the `create` method so that we can use them in our app. + +```python +from reflex.components.component import Component +from typing import Any, Dict, List, Union +from reflex.vars import Var + +class ReactFlowLib(Component): + ... + +class ReactFlow(ReactFlowLib): + ... + +class Background(ReactFlowLib): + + tag = "Background" + + color: Var[str] + + gap: Var[int] + + size: Var[int] + + variant: Var[str] + +class Controls(ReactFlowLib): + + tag = "Controls" + +react_flow = ReactFlow.create +background = Background.create +controls = Controls.create +``` + +## Building the App + +Now that we have our components lets build the app. + +Lets start by defining the initial nodes and edges that we will use in our app. + +```python +import reflex as rx +from .react_flow import react_flow, background, controls +import random +from typing import Any, Dict, List + + +initial_nodes = [ + \{ + 'id': '1', + 'type': 'input', + 'data': \{'label': '150'}, + 'position': \{'x': 250, 'y': 25}, + }, + \{ + 'id': '2', + 'data': \{'label': '25'}, + 'position': \{'x': 100, 'y': 125}, + }, + \{ + 'id': '3', + 'type': 'output', + 'data': \{'label': '5'}, + 'position': \{'x': 250, 'y': 250}, + }, +] + +initial_edges = [ + \{'id': 'e1-2', 'source': '1', 'target': '2', 'label': '*', 'animated': True}, + \{'id': 'e2-3', 'source': '2', 'target': '3', 'label': '+', 'animated': True}, +] +``` + +Next we will define the state of our app. We have three event handlers: `add_random_node`, `clear_graph`, and `on_edges_change`. + +The `on_edges_change` event handler will be called when an edge is changed. In this case we will use it to delete an edge if it already exists, and add the new edge. It takes in a single argument `new_edge` which is a dictionary containing the `source` and `target` of the edge. + +```python +class State(rx.State): + """The app state.""" + nodes: List[Dict[str, Any]] = initial_nodes + edges: List[Dict[str, Any]] = initial_edges + + def add_random_node(self): + new_node_id = f'\{len(self.nodes) + 1\}' + node_type = random.choice(['default']) + # Label is random number + label = new_node_id + x = random.randint(0, 500) + y = random.randint(0, 500) + + new_node = { + 'id': new_node_id, + 'type': node_type, + 'data': \{'label': label}, + 'position': \{'x': x, 'y': y}, + 'draggable': True, + } + self.nodes.append(new_node) + + def clear_graph(self): + self.nodes = [] # Clear the nodes list + self.edges = [] # Clear the edges list + + def on_edges_change(self, new_edge): + # Iterate over the existing edges + for i, edge in enumerate(self.edges): + # If we find an edge with the same ID as the new edge + if edge["id"] == f"e\{new_edge['source']}-\{new_edge['target']}": + # Delete the existing edge + del self.edges[i] + break + + # Add the new edge + self.edges.append({ + "id": f"e\{new_edge['source']}-\{new_edge['target']}", + "source": new_edge["source"], + "target": new_edge["target"], + "label": random.choice(["+", "-", "*", "/"]), + "animated": True, + }) +``` + +Now lets define the UI of our app. We will use the `react_flow` component and pass in the `nodes` and `edges` from our state. We will also add the `on_connect` event handler to the `react_flow` component to handle when an edge is connected. + +```python +def index() -> rx.Component: + return rx.vstack( + react_flow( + background(), + controls(), + nodes_draggable=True, + nodes_connectable=True, + on_connect=lambda e0: State.on_edges_change(e0), + nodes=State.nodes, + edges=State.edges, + fit_view=True, + ), + rx.hstack( + rx.button("Clear graph", on_click=State.clear_graph, width="100%"), + rx.button("Add node", on_click=State.add_random_node, width="100%"), + width="100%", + ), + height="30em", + width="100%", + ) + + +# Add state and page to the app. +app = rx.App() +app.add_page(index) +``` + +```python exec +import reflex as rx +from reflex.components.component import Component +from typing import Any, Dict, List, Union +from reflex.vars import Var +import random + +class ReactFlowLib(Component): + """A component that wraps a react flow lib.""" + + library = "reactflow" + + def _get_custom_code(self) -> str: + return """import 'reactflow/dist/style.css'; + """ + +class ReactFlow(ReactFlowLib): + + tag = "ReactFlow" + + nodes: Var[List[Dict[str, Any]]] + + edges: Var[List[Dict[str, Any]]] + + fit_view: Var[bool] + + nodes_draggable: Var[bool] + + nodes_connectable: Var[bool] + + nodes_focusable: Var[bool] + + def get_event_triggers(self) -> dict[str, Any]: + return { + **super().get_event_triggers(), + "on_edges_change": lambda e0: [e0], + "on_connect": lambda e0: [e0], + } + + +class Background(ReactFlowLib): + + tag = "Background" + + color: Var[str] + + gap: Var[int] + + size: Var[int] + + variant: Var[str] + +class Controls(ReactFlowLib): + + tag = "Controls" + +react_flow = ReactFlow.create +background = Background.create +controls = Controls.create + +initial_nodes = [ + { + 'id': '1', + 'type': 'input', + 'data': {'label': '150'}, + 'position': {'x': 250, 'y': 25}, + }, + { + 'id': '2', + 'data': {'label': '25'}, + 'position': {'x': 100, 'y': 125}, + }, + { + 'id': '3', + 'type': 'output', + 'data': {'label': '5'}, + 'position': {'x': 250, 'y': 250}, + }, +] + +initial_edges = [ + {'id': 'e1-2', 'source': '1', 'target': '2', 'label': '*', 'animated': True}, + {'id': 'e2-3', 'source': '2', 'target': '3', 'label': '+', 'animated': True}, +] + + +class ReactFlowState(rx.State): + """The app state.""" + nodes: List[Dict[str, Any]] = initial_nodes + edges: List[Dict[str, Any]] = initial_edges + + def add_random_node(self): + new_node_id = f'{len(self.nodes) + 1}' + node_type = random.choice(['default']) + # Label is random number + label = new_node_id + x = random.randint(0, 250) + y = random.randint(0, 250) + + new_node = { + 'id': new_node_id, + 'type': node_type, + 'data': {'label': label}, + 'position': {'x': x, 'y': y}, + 'draggable': True, + } + print(new_node) + self.nodes.append(new_node) + + def clear_graph(self): + self.nodes = [] # Clear the nodes list + self.edges = [] # Clear the edges list + + def on_edges_change(self, new_edge): + # Iterate over the existing edges + for i, edge in enumerate(self.edges): + # If we find an edge with the same ID as the new edge + if edge["id"] == f"e{new_edge['source']}-{new_edge['target']}": + # Delete the existing edge + del self.edges[i] + break + + # Add the new edge + self.edges.append({ + "id": f"e{new_edge['source']}-{new_edge['target']}", + "source": new_edge["source"], + "target": new_edge["target"], + "label": random.choice(["+", "-", "*", "/"]), + "animated": True, + }) +``` + +Here is an example of the app running: + +```python eval +rx.vstack( + react_flow( + background(), + controls(), + nodes_draggable=True, + nodes_connectable=True, + on_connect=lambda e0: ReactFlowState.on_edges_change(e0), + nodes=ReactFlowState.nodes, + edges=ReactFlowState.edges, + fit_view=True, + ), + rx.hstack( + rx.button("Clear graph", on_click=ReactFlowState.clear_graph, width="100%"), + rx.button("Add node", on_click=ReactFlowState.add_random_node, width="100%"), + width="100%", + ), + height="30em", + width="100%", + ) +``` diff --git a/docs/wrapping-react/imports.md b/docs/wrapping-react/imports.md new file mode 100644 index 000000000..12c9edbc3 --- /dev/null +++ b/docs/wrapping-react/imports.md @@ -0,0 +1,146 @@ +```python exec +import reflex as rx +from typing import Any +``` + +## Basics of Imports + +Before deciding to extend Reflex by wrapping a component, check to see if there is a corresponding, well maintained React library. Search for it on [npm](https://www.npmjs.com/), and if it's there, you can use it in your Reflex app. + +```javascript +import \{ HexColorPicker } from "react-colorful" +``` + +The two main things we need to know when wrapping a React component are the library and the tag. + +The library is the name of the npm package, and the tag is the name of the React component. As seen in the example above, the library is `react-colorful` and the tag is `HexColorPicker`. + +The corresponding Reflex component would look like this: + +```python +class ColorPicker(rx.Component): + """Color picker component.""" + + library = "react-colorful" + tag = "HexColorPicker" +``` + +## Import Types + +If the tag is the default export from the module, you can set `is_default = True` in your component class. This is normally used when components don't have curly braces around them when importing. + +We do this in the Spline example in the overview section: + +```javascript +import Spline from '@splinetool/react-spline'; +``` + +To wrap this component we would set `is_default = True` in our component class: + +```python +class Spline(rx.Component): + """Spline component.""" + + library = "@splinetool/react-spline" + tag = "Spline" + scene: Var[str] = "https://prod.spline.design/Br2ec3WwuRGxEuij/scene.splinecode" + is_default = True + + lib_dependencies: list[str] = ["@splinetool/runtime"] +``` + +## Library Dependencies + +By default Reflex will install the library you have specified in the library property. However, sometimes you may need to install other libraries to use a component. In this case you can use the `lib_dependencies` property to specify other libraries to install. + +As seen in the Spline example in the overview section, we need to import the `@splinetool/runtime` library to use the `Spline` component. We can specify this in our component class like this: + +```python +class Spline(rx.Component): + """Spline component.""" + + library = "@splinetool/react-spline" + tag = "Spline" + scene: Var[str] = "https://prod.spline.design/Br2ec3WwuRGxEuij/scene.splinecode" + is_default = True + + lib_dependencies: list[str] = ["@splinetool/runtime"] +``` + +## Aliases + +If you are wrapping another components with the same tag as a component in your project you can use aliases to differentiate between them and avoid naming conflicts. + +Lets check out the code below, in this case if we needed to wrap another color picker library with the same tag we use an alias to avoid a conflict. + +```python +class AnotherColorPicker(rx.Component): + library = "some-other-colorpicker" + tag = "HexColorPicker" + alias = "OtherHexColorPicker" + color: rx.Var[str] + + def get_event_triggers(self) -> dict[str, Any]: + return \{ + **super().get_event_triggers(), + "on_change": lambda e0: [e0], + } +``` + +## Dynamic Imports + +Some libraries you may want to wrap may require dynamic imports. This is because they they may not be compatible with Server-Side Rendering (SSR). + +To handle this in Reflex all you need to do is subclass `NoSSRComponent` when defining your component. + +Often times when you see an import something like this: + +```javascript +import dynamic from 'next/dynamic'; + +const MyLibraryComponent = dynamic(() => import('./MyLibraryComponent'), { + ssr: false +}); +``` + +You can wrap it in Reflex like this, here we are wrapping the `react-plotly.js` library which requires dynamic imports: + +```python +from reflex.components.component import NoSSRComponent + +class PlotlyLib(NoSSRComponent): + """A component that wraps a plotly lib.""" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: List[str] = ["plotly.js@2.22.0"] +``` + +## Custom Code + +Sometimes you may need to add custom code to your component. This could be anything from adding custom constants, to adding custom imports, or even adding custom functions. Custom code will be inserted _outside_ of the react component function. + +```javascript +import React from "react"; +// Other imports... +... + +// Custom code +const customCode = "const customCode = 'customCode';"; +``` + +To add custom code to your component you can use the `get_custom_code` method in your component class. + +```python +from reflex.components.component import NoSSRComponent + +class PlotlyLib(NoSSRComponent): + """A component that wraps a plotly lib.""" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: List[str] = ["plotly.js@2.22.0"] + + def _get_custom_code(self) -> str: + return "const customCode = 'customCode';" +``` diff --git a/docs/wrapping-react/logic.md b/docs/wrapping-react/logic.md new file mode 100644 index 000000000..781b68dcc --- /dev/null +++ b/docs/wrapping-react/logic.md @@ -0,0 +1,109 @@ +```python exec +import reflex as rx +from typing import Any +from pcweb.pages.docs import state +``` + +## Declaring Vars + +As seen in our [state section]({state.overview.path}), we can use `rx.Var` to define state variables in our Reflex apps. + +When wrapping your own react components, you can use `rx.Var` to define props for your component. In the example below, we define a `color` prop for our `ColorPicker` component. + +```python +class ColorPicker(rx.Component): + library = "react-colorful" + tag = "HexColorPicker" + color: rx.Var[str] +``` + +However, vars can be more than just a sigle type. You var can be any combination of primitive types. + +```python +class SomeComponent(rx.Component): + + tag = "SomeComponent" + + data: Var[List[Dict[str, Any]]] +``` + +Here we define a var that is a list of dictionaries. This is useful for when you want to pass in a list of data to your component. + +## Serializing Vars + +Sometimes you want to create a var that isn't a common primitive type. In this case, you can use the `serializer` to convert your var to a primitive type which can be stored in your state. + +Here is an example of how we can serialize a plotly figure into a json which can be stored in our state. + +```python +import json +from typing import Any, Dict, List + +from reflex.components.component import NoSSRComponent +from reflex.utils.serializers import serializer +from reflex.vars import Var + +try: + from plotly.graph_objects import Figure +except ImportError: + Figure = Any + +class PlotlyLib(NoSSRComponent): + """A component that wraps a plotly lib.""" + + library = "react-plotly.js@2.6.0" + + lib_dependencies: List[str] = ["plotly.js@2.22.0"] + + +class Plotly(PlotlyLib): + """Display a plotly graph.""" + + tag = "Plot" + + is_default = True + + # The figure to display. This can be a plotly figure or a plotly data json. + data: Var[Figure] + + ... + + +try: + from plotly.graph_objects import Figure + from plotly.io import to_json + + @serializer + def serialize_figure(figure: Figure) -> list: + """Serialize a plotly figure. + + Args: + figure: The figure to serialize. + + Returns: + The serialized figure. + """ + return json.loads(str(to_json(figure)))["data"] + +except ImportError: + pass +``` + +## Event Triggers + +As seen in our [events section](https://reflex.dev/docs/state/events/), we can use event triggers to handle events in our Reflex apps. When wrapping your own react components, you can use the `get_event_triggers` method to define event triggers for your component. + +Sometimes these event trigger may take in arguments, for example, the `on_change` event trigger for the `HexColorPicker` component we saw in the [wrapping react section](https://reflex.dev/docs/wrapping-react/wrapping-react/). In this case, we can use a lambda function to pass in the event argument to the event trigger. The function associated with a trigger maps args for the javascript trigger to args that will be passed to the backend event handler function. + +```python +class ColorPicker(rx.Component): + library = "react-colorful" + tag = "HexColorPicker" + color: rx.Var[str] + + def get_event_triggers(self) -> dict[str, Any]: + return \{ + **super().get_event_triggers(), + "on_change": lambda e0: [e0], + } +``` diff --git a/docs/wrapping-react/overview.md b/docs/wrapping-react/overview.md new file mode 100644 index 000000000..6bc6542fc --- /dev/null +++ b/docs/wrapping-react/overview.md @@ -0,0 +1,163 @@ +```python exec +import reflex as rx +from typing import Any +from pcweb.components.spline import spline +from pcweb.templates.docpage import demo_box_style +from pcweb import constants +``` + +# Wrapping React Overview + +One of Reflex's most powerful features is the ability to wrap React components. This allows us to build on top of the existing React ecosystem, and leverage the vast array of existing React components and libraries. + +If you want a specific component for your app but Reflex doesn't provide it, there's a good chance it's available as a React component. Search for it on [npm]({constants.NPMJS_URL}), and if it's there, you can use it in your Reflex app. + +In this section, we'll go over how to wrap React components on a high level. In the subsequent sections, we'll go over the details of how to wrap more complex components. + +## Spline Example + +Let's start with a library called [Spline]({constants.SPLINE_URL}). Spline is a tool for creating 3D scenes and animations. It's a great tool for creating interactive 3D visualizations. + +We have some react code that creates a Spline scene. We want to wrap this code in a Reflex component so that we can use it in our Reflex app. + +```javascript +import Spline from '@splinetool/react-spline'; + +export default function App() { + return ( + + ); +} +``` + +Here is how we would wrap this component in Reflex. + +The two most important props are `library`, which is the name of the npm package, and `tag`, which is the name of the React component. + +```python +class Spline(rx.Component): + """Spline component.""" + + library = "@splinetool/react-spline" + tag = "Spline" + scene: Var[str] = "https://prod.spline.design/Br2ec3WwuRGxEuij/scene.splinecode" + is_default = True + + lib_dependencies: list[str] = ["@splinetool/runtime"] +``` + +Here the library is `@splinetool/react-spline` and the tag is `Spline`. In the next section we will go into a deep dive on imports but we also set `is_default = True` because the tag is the default export from the module. + +Additionally, we can specify any props that the component takes. In this case, the `Spline` component takes a `scene` prop, which is the URL of the Spline scene. + +## Full Example + +```python eval +rx.center( + spline( + scene="https://prod.spline.design/joLpOOYbGL-10EJ4/scene.splinecode" + ), + overflow="hidden", + width="100%", + height="30em", + padding="0", + margin_bottom="1em", + style=demo_box_style, + ) +``` + +```python +class Spline(rx.Component): + """Spline component.""" + + library = "@splinetool/react-spline" + tag = "Spline" + scene: Var[str] ="https://prod.spline.design/joLpOOYbGL-10EJ4/scene.splinecode" + is_default = True + + lib_dependencies: list[str] = ["@splinetool/runtime"] + +spline = Spline.create + +def spline_example(): + return rx.center( + spline(), + overflow="hidden", + width="100%", + height="30em", + ) +``` + +## ColorPicker Example + +Similar to the Spline example we start with defining the library and tag. In this case the library is `react-colorful` and the tag is `HexColorPicker`. + +We also have a var `color` which is the current color of the color picker. + +Since this component has interaction we must specify any event triggers that the component takes. The color picker has a single trigger `on_change` to specify when the color changes. This trigger takes in a single argument `color` which is the new color. Here `super().get_event_triggers()` is used to get the default event triggers for all components. + +```python exec +class ColorPicker(rx.Component): + library = "react-colorful" + tag = "HexColorPicker" + color: rx.Var[str] + + def get_event_triggers(self) -> dict[str, Any]: + return { + **super().get_event_triggers(), + "on_change": lambda e0: [e0], + } + +color_picker = ColorPicker.create + + +class ColorPickerState(rx.State): + color: str = "#db114b" +``` + +```python eval +rx.box( + rx.vstack( + rx.heading(ColorPickerState.color, color="white"), + color_picker( + on_change=ColorPickerState.set_color + ), + ), + background_color=ColorPickerState.color, + padding="5em", + border_radius="1em", + margin_bottom="1em", + ) +``` + +```python +class ColorPicker(rx.Component): + library = "react-colorful" + tag = "HexColorPicker" + color: rx.Var[str] + + def get_event_triggers(self) -> dict[str, Any]: + return \{ + **super().get_event_triggers(), + "on_change": lambda e0: [e0], + \} + +color_picker = ColorPicker.create + +class ColorPickerState(rx.State): + color: str = "#db114b" + +def index(): + return rx.box( + rx.vstack( + rx.heading(ColorPickerState.color, color="white"), + color_picker( + on_change=ColorPickerState.set_color + ), + ), + background_color=ColorPickerState.color, + padding="5em", + border_radius="1em", + ) + +```