From aa2a1df201e67fd0cfd2641909bd2bfe8a191fc6 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Sun, 11 Jun 2023 23:28:33 -0700 Subject: [PATCH] Add vars and components for working with color_mode (#1132) * `pc.color_mode`: a BaseVar that accesses colorMode on the frontend * `pc.color_mode_cond`: a `pc.cond` wrapper that conditionally renders components or props based on the value of `color_mode` * `pc.color_mode_icon`: by default "sun" if light mode, "moon" if dark mode * `pc.color_mode_switch`: a Switch component where is_checked depends on the color_mode and changing the value calls toggle_color_mode * `pc.color_mode_button`: a Button component that calls toggle_color_mode on click The default template has been updated to include a color_mode_button with color_mode_icon for toggling light/dark mode. The inline hover style has also been updated to use color_mode_cond to show a different highlight color based on the color_mode. --- pynecone/.templates/apps/default/default.py | 10 +- pynecone/__init__.py | 1 + pynecone/components/__init__.py | 3 + pynecone/components/forms/__init__.py | 12 +- pynecone/components/forms/colormodeswitch.py | 116 +++++++++++++++++++ pynecone/style.py | 1 + 6 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 pynecone/components/forms/colormodeswitch.py diff --git a/pynecone/.templates/apps/default/default.py b/pynecone/.templates/apps/default/default.py index 5a536f9a4..27bad540e 100644 --- a/pynecone/.templates/apps/default/default.py +++ b/pynecone/.templates/apps/default/default.py @@ -14,7 +14,8 @@ class State(pc.State): def index() -> pc.Component: - return pc.center( + return pc.fragment( + pc.color_mode_button(pc.color_mode_icon(), float="right"), pc.vstack( pc.heading("Welcome to Pynecone!", font_size="2em"), pc.box("Get started by editing ", pc.code(filename, font_size="1em")), @@ -25,13 +26,16 @@ def index() -> pc.Component: padding="0.5em", border_radius="0.5em", _hover={ - "color": "rgb(107,99,246)", + "color": pc.color_mode_cond( + light="rgb(107,99,246)", + dark="rgb(179, 175, 255)", + ) }, ), spacing="1.5em", font_size="2em", + padding_top="10%", ), - padding_top="10%", ) diff --git a/pynecone/__init__.py b/pynecone/__init__.py index c3f288a5b..0341a890c 100644 --- a/pynecone/__init__.py +++ b/pynecone/__init__.py @@ -32,6 +32,7 @@ from .model import session as session from .route import route as route from .state import ComputedVar as var from .state import State as State +from .style import color_mode as color_mode from .style import toggle_color_mode as toggle_color_mode from .vars import Var as Var from .vars import cached_var as cached_var diff --git a/pynecone/components/__init__.py b/pynecone/components/__init__.py index 9c34bc51b..9e4815969 100644 --- a/pynecone/components/__init__.py +++ b/pynecone/components/__init__.py @@ -238,3 +238,6 @@ text = Text.create script = ScriptTag.create aspect_ratio = AspectRatio.create kbd = KeyboardKey.create +color_mode_button = ColorModeButton.create +color_mode_icon = ColorModeIcon.create +color_mode_switch = ColorModeSwitch.create diff --git a/pynecone/components/forms/__init__.py b/pynecone/components/forms/__init__.py index 57c5e36ba..37a59879d 100644 --- a/pynecone/components/forms/__init__.py +++ b/pynecone/components/forms/__init__.py @@ -2,6 +2,12 @@ from .button import Button, ButtonGroup from .checkbox import Checkbox, CheckboxGroup +from .colormodeswitch import ( + ColorModeButton, + ColorModeIcon, + ColorModeSwitch, + color_mode_cond, +) from .copytoclipboard import CopyToClipboard from .date_picker import DatePicker from .date_time_picker import DateTimePicker @@ -34,4 +40,8 @@ from .switch import Switch from .textarea import TextArea from .upload import Upload -__all__ = [f for f in dir() if f[0].isupper()] # type: ignore +helpers = [ + "color_mode_cond", +] + +__all__ = [f for f in dir() if f[0].isupper()] + helpers # type: ignore diff --git a/pynecone/components/forms/colormodeswitch.py b/pynecone/components/forms/colormodeswitch.py new file mode 100644 index 000000000..f0eadda38 --- /dev/null +++ b/pynecone/components/forms/colormodeswitch.py @@ -0,0 +1,116 @@ +"""A switch component for toggling color_mode. + +To style components based on color mode, use style props with `color_mode_cond`: + +``` +pc.text( + "Hover over me", + _hover={ + "background": pc.color_mode_cond( + light="var(--chakra-colors-gray-200)", + dark="var(--chakra-colors-gray-700)", + ), + }, +) +``` +""" +from __future__ import annotations + +from typing import Any + +from pynecone.components.component import Component +from pynecone.components.layout.cond import Cond, cond +from pynecone.components.media.icon import Icon +from pynecone.style import color_mode, toggle_color_mode +from pynecone.vars import BaseVar + +from .button import Button +from .switch import Switch + +DEFAULT_COLOR_MODE = "light" +DEFAULT_LIGHT_ICON = Icon.create(tag="sun") +DEFAULT_DARK_ICON = Icon.create(tag="moon") + + +def color_mode_cond(light: Any, dark: Any = None) -> BaseVar | Component: + """Create a component or Prop based on color_mode. + + Args: + light: The component or prop to render if color_mode is default + dark: The component or prop to render if color_mode is non-default + + Returns: + The conditional component or prop. + """ + return cond( + color_mode == DEFAULT_COLOR_MODE, + light, + dark, + ) + + +class ColorModeIcon(Cond): + """Displays the current color mode as an icon.""" + + @classmethod + def create( + cls, + light_component: Component | None = None, + dark_component: Component | None = None, + ): + """Create an icon component based on color_mode. + + Args: + light_component: the component to display when color mode is default + dark_component: the component to display when color mode is dark (non-default) + + Returns: + The conditionally rendered component + """ + return color_mode_cond( + light=light_component or DEFAULT_LIGHT_ICON, + dark=dark_component or DEFAULT_DARK_ICON, + ) + + +class ColorModeSwitch(Switch): + """Switch for toggling chakra light / dark mode via toggle_color_mode.""" + + @classmethod + def create(cls, *children, **props): + """Create a switch component bound to color_mode. + + Args: + *children: The children of the component. + **props: The props to pass to the component. + + Returns: + The switch component. + """ + return Switch.create( + *children, + is_checked=color_mode != DEFAULT_COLOR_MODE, + on_change=toggle_color_mode, + **props, + ) + + +class ColorModeButton(Button): + """Button for toggling chakra light / dark mode via toggle_color_mode.""" + + @classmethod + def create(cls, *children, **props): + """Create a button component that calls toggle_color_mode on click. + + Args: + *children: The children of the component. + **props: The props to pass to the component. + + Returns: + The switch component. + """ + return Button.create( + *children, + on_click=toggle_color_mode, + **props, + ) diff --git a/pynecone/style.py b/pynecone/style.py index 11f341c53..64857bf16 100644 --- a/pynecone/style.py +++ b/pynecone/style.py @@ -7,6 +7,7 @@ from pynecone.event import EventChain from pynecone.utils import format from pynecone.vars import BaseVar, Var +color_mode = BaseVar(name=constants.COLOR_MODE, type_="str") toggle_color_mode = BaseVar(name=constants.TOGGLE_COLOR_MODE, type_=EventChain)