Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REF-2574] Default width for Stack (+children) and default padding for container #3104

Merged
merged 10 commits into from
Apr 30, 2024

Conversation

masenf
Copy link
Collaborator

@masenf masenf commented Apr 17, 2024

Stack Width

Opt-in to 2 tweaks to improve out of the box experience using layout stacks via the new rx.style.STACK_CHILDREN_FULL_WIDTH style snippet

  • Width is 100%
  • Width of child div (except Box, Upload, and Html), table, and other form controls is 100%

Having a default width of 100% makes components fit nicely in their parent container. This prop often needs to be specified to have things look aligned when placing elements in a stack.

Container prop: stack_children_full_width

An easy way to get the above behavior in your top level container is by passing stack_children_full_width=True. Eventually the experimental opinionated layout component may treat this as its default, but rx.container will remain unopinionated about the width of its children.

Container Padding

The rx.container centers content to a particular width, however when the viewport is less than that width, the content runs directly to the edge, which isn't very aesthetically pleasing. The default padding of 16px means that viewports narrower than the container still get some space on the edge to breathe.

Reducing the default size of the container from "4" to "3" fits better with more screen sizes and is easily changed for apps that want a wider content area.

Results

Sample Code
"""Welcome to Reflex! This file outlines the steps to create a basic app."""

from rxconfig import config

import reflex as rx

docs_url = "https://reflex.dev/docs/getting-started/introduction/"
filename = f"{config.app_name}/{config.app_name}.py"


class State(rx.State):
    """The app state."""


def buttons_card():
    return rx.vstack(
        "Buttons",
        rx.divider(),
        rx.button("Yay"),
        rx.button("Party on"),
        rx.hstack(
            rx.button("Hello"),
            rx.button("world"),
        ),
        rx.card(
            rx.vstack(
                "Child",
                rx.button("Raise"),
            ),
        ),
        rx.hstack(
            rx.icon_button(rx.icon("pencil")),
            rx.icon_button(rx.icon("x"), color_scheme="tomato"),
            rx.icon_button(rx.icon("check"), color_scheme="green"),
            rx.button("Skip", variant="outline"),
        ),
        rx.hstack(
            rx.button("1", color_scheme="crimson"),
            rx.button("2", color_scheme="plum", width="20%"),
            rx.button("3", color_scheme="lime", width="20%"),
        ),
    )


def form_with_labels_card():
    return rx.form(
        rx.vstack(
            "Controls with Labels",
            rx.divider(),
            rx.progress(
                value=45,
            ),
            rx.form.field(
                rx.form.label("How much?"),
                rx.slider(default_value=[24]),
            ),
            rx.form.field(
                rx.form.label("Something to choose"),
                rx.select(
                    items=["One", "Two", "Three"],
                    placeholder="Select an item",
                ),
            ),
            rx.form.field(
                rx.form.label("Input"),
                rx.input(
                    placeholder="Type something",
                ),
            ),
            rx.form.field(
                rx.form.label("This input grows with you"),
                rx.text_area(
                    auto_height=True,
                    rows="1",
                    min_height="0px",
                ),
            ),
            rx.button("Submit"),
        ),
    )


def plain_controls_card():
    return rx.vstack(
        "Plain Controls",
        rx.divider(),
        rx.card(
            rx.vstack(
                rx.hstack(
                    rx.avatar(src="https://reflex.dev/logo.jpg", fallback="R"),
                    rx.text("Reflex User"),
                ),
                rx.hstack(
                    rx.text(
                        "Here's a little bit about the user that you might want to know.",
                        size="1",
                    ),
                    rx.icon_button(
                        rx.icon("pencil"),
                        variant="outline",
                    ),
                ),
            ),
        ),
        rx.progress(
            value=85,
            color_scheme="green",
        ),
        rx.hstack(
            rx.badge("new"),
            rx.badge("fancy"),
            rx.badge("pretty", color_scheme="gold"),
        ),
        rx.slider(default_value=[24]),
        rx.select(
            items=["One", "Two", "Three"],
            placeholder="Select an item",
        ),
        rx.input(
            placeholder="Type something",
        ),
        rx.text_area(
            auto_height=True,
            rows="1",
            min_height="0px",
        ),
        rx.button("Submit"),
    )

def data_display_card():
    return rx.vstack(
        "Data Display",
        rx.divider(),
        rx.callout("This is a callout!"),
        rx.table.root(
            rx.table.header(
                rx.table.row(
                    rx.table.column_header_cell("Name"),
                    rx.table.column_header_cell("Age"),
                ),
            ),
            rx.table.body(
                rx.table.row(
                    rx.table.cell("Alice"),
                    rx.table.cell("25"),
                ),
                rx.table.row(
                    rx.table.cell("Bob"),
                    rx.table.cell("30"),
                ),
                rx.table.row(
                    rx.table.cell("Charlie"),
                    rx.table.cell("35"),
                ),
            ),
        ),
        rx.hstack(
            rx.badge("metal"),
            rx.badge("pop punk"),
            rx.badge("country"),
            rx.badge("hip hop"),
            flex_wrap="wrap",
        ),
        rx.scroll_area(
            rx.text(
                """We the People of the United States, in Order to form a more perfect Union, establish Justice, insure domestic Tranquility, provide for the common defence, promote the general Welfare, and secure the Blessings of Liberty to ourselves and our Posterity, do ordain and establish this Constitution for the United States of America.""",
                size="1",
            ),
            height="80px",
        ),
        rx.list.unordered(
            items=[
                "Alice",
                "Bob",
                "Charlie",
                "John Jacob Jingleheimer Schmidt (that's my name too)",
            ],
        ),
    )


def icon_palette_card():
    return rx.vstack(
        "Icons",
        rx.divider(),
        rx.hstack(
            rx.icon("pencil"),
            rx.icon("trash"),
            rx.icon("octagon_alert"),
            rx.icon("check"),
            rx.icon("x"),
            rx.icon("save"),
            rx.icon("search"),
            rx.icon("accessibility"),
            rx.icon("activity"),
            rx.icon("mail"),
            flex_wrap="wrap",
        ),
    )

def accordion_card():
    return rx.vstack(
        "Accordion",
        rx.divider(),
        rx.accordion.root(
            rx.accordion.item(
                header="Accordion Item 1",
                content=rx.text("Content 1"),
            ),
            rx.accordion.item(
                header="Accordion Item 2",
                content=rx.text("Content 2"),
            ),
            rx.accordion.item(
                header="Accordion Item 3",
                content=rx.text("Content 3"),
            ),
            collapsible=True,
            variant="outline",
        ),
    )


def tabs_card():
    return rx.tabs.root(
        rx.tabs.list(
            rx.tabs.trigger("Foo", value="foo", color_scheme="green"),
            rx.tabs.trigger("Bar", value="bar", color_scheme="red"),
        ),
        rx.tabs.content(
            rx.scroll_area(
                rx.text(
                    """"Foo" is a placeholder name commonly used in computer programming, mathematics, and other technical contexts. It's often used in examples and illustrations when the specific name is not relevant to the concept being discussed. Along with "foo," you might also encounter "bar" and "baz" used in a similar manner. These placeholders help to focus on the structure or logic of the example rather than the specifics of the names involved.""",
                ),
                height="300px",
            ),
            value="foo",
        ),
        rx.tabs.content(
            rx.scroll_area(
                rx.text(
                    """The term "bar" can refer to various things depending on the context:""",
                ),
                rx.list.ordered(
                    items=[
                        """Drinking establishment: A bar is a place where alcoholic beverages are served for consumption on the premises. It's a social venue where people gather to relax, socialize, and enjoy drinks.""",
                        """Unit of Pressure: In physics and engineering, a bar is a unit of pressure. One bar is equivalent to 100,000 pascals, which is roughly equal to atmospheric pressure at sea level.""",
                        """Legal profession: In law, "bar" can refer to the legal profession as a whole, or more specifically, to the barrier or railing separating the area in a courtroom where lawyers, judges, and other legal professionals sit from the rest of the courtroom.""",
                        """Music: In musical notation, a bar (or measure) is a segment of time corresponding to a specific number of beats in which each beat is represented by a particular note value.""",
                        """Solid object: A bar can also refer to a long, narrow, rigid piece of material, such as a metal bar or a candy bar.""",
                    ],
                ),
                height="300px",
            ),
            value="bar",
        ),
        default_value="foo",
    )

def other_form_controls():
    return rx.vstack(
        "Other Form Controls",
        rx.divider(),
        rx.checkbox("Do you Accept?"),
        rx.hstack(
            rx.switch(),
            rx.text("More privacy?", size="2"),
            align="center",
        ),
        rx.radio(
            items=[
                "Option 1",
                "Option 2",
                "Option 3",
            ],
        ),
    )

def charts_card(**props):
    data01 = [
        {"name": "Group A", "value": 400, "fill": rx.color("accent", 10)},
        {"name": "Group B", "value": 300, "fill": rx.color("accent", 9)},
        {"name": "Group C", "value": 300, "fill": rx.color("accent", 8)},
        {"name": "Group D", "value": 200, "fill": rx.color("accent", 7)},
        {"name": "Group E", "value": 278, "fill": rx.color("accent", 6)},
        {"name": "Group F", "value": 189, "fill": rx.color("accent", 5)},
    ]
    data02 = [
        {"name": "Group H", "value": 240, "fill": rx.color("accent", 5)},
        {"name": "Group I", "value": 456, "fill": rx.color("accent", 6)},
        {"name": "Group J", "value": 139, "fill": rx.color("accent", 7)},
        {"name": "Group K", "value": 980, "fill": rx.color("accent", 8)},
        {"name": "Group L", "value": 390, "fill": rx.color("accent", 9)},
        {"name": "Group M", "value": 480, "fill": rx.color("accent", 10)},
    ]
    range_data = [
        {"day": "05-01", "temperature": [-1, 10], "fill": rx.color("accent", 1)},
        {"day": "05-02", "temperature": [2, 15], "fill": rx.color("accent", 2)},
        {"day": "05-03", "temperature": [3, 12], "fill": rx.color("accent", 3)},
        {"day": "05-04", "temperature": [4, 12], "fill": rx.color("accent", 4)},
        {"day": "05-05", "temperature": [12, 16], "fill": rx.color("accent", 5)},
        {"day": "05-06", "temperature": [5, 16], "fill": rx.color("accent", 6)},
        {"day": "05-07", "temperature": [3, 12], "fill": rx.color("accent", 7)},
        {"day": "05-08", "temperature": [0, 8], "fill": rx.color("accent", 8)},
        {"day": "05-09", "temperature": [-3, 5], "fill": rx.color("accent", 9)},
    ]
    sdata01 = [
        {"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},
    ]
    sdata02 = [
        {"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},
    ]
    return rx.vstack(
        "Charts",
        rx.divider(),
        rx.recharts.pie_chart(
            rx.recharts.pie(
                data=data01,
                data_key="value",
                name_key="name",
                cx="75%",
                cy="50%",
                fill="#8dd1e1",
                label=True,
            ),
            rx.recharts.pie(
                data=data02,
                data_key="value",
                name_key="name",
                cx="20%",
                cy="50%",
                fill="#8dd1e1",
                label=True,
            ),
            **props,
        ),
        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,
            **props,
        ),
        rx.recharts.scatter_chart(
            rx.recharts.scatter(
                data=sdata01, fill="#8884d8", name="A"
            ),
            rx.recharts.scatter(
                data=sdata02, 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(),
            **props,
        )
    )

def fib_dp_code():
    return rx.vstack(
        rx.text("Implementing Fibonacci with Dynamic Programming"),
        rx.code_block(
            """cache = {0: 0, 1: 1}

def fibonacci_dp(n):
    if n in cache:  # Base case
        return cache[n]
    # Compute and cache the Fibonacci number
    cache[n] = fibonacci_of(n - 1) + fibonacci_of(n - 2)  # Recursive case
    return cache[n]
""",
            max_width="100%",
        ),
        align="center",
    )

def content() -> rx.Component:
    return rx.vstack(
        rx.heading("Welcome to Reflex!", size="9"),
        rx.text("Get started by editing ", rx.code(filename), font_size="2em"),
        rx.button(
            "Check out our docs!",
            on_click=lambda: rx.redirect(docs_url),
            size="4",
        ),
        rx.hstack(
            rx.card(buttons_card()),
            rx.card(form_with_labels_card()),
            rx.card(plain_controls_card()),
        ),
        rx.divider(),
        fib_dp_code(),
        rx.divider(),
        rx.hstack(
            rx.card(data_display_card()),
            rx.vstack(
                rx.card(icon_palette_card()),
                rx.card(accordion_card()),
                rx.card(tabs_card()),
            ),
            rx.vstack(
                rx.card(other_form_controls()),
                rx.card(charts_card()),
            ),
        ),
        rx.hstack(
            rx.button("Hello"),
            rx.button("World"),
            width="50%",
        ),
        rx.vstack(
            rx.upload(
                rx.button("Upload My Dude"),
                class_name=["foo", "bar"],
            ),
            rx.box(
                rx.text("Foo"),
                rx.text("Mcgoo"),
            ),
            rx.button("Button in stack"),
            rx.button("Button with width", width="auto"),
            rx.box(rx.button("Button in box")),
            align="center",
        ),
        rx.box(
            charts_card(height=300),
            width="75%",
        ),
        rx.logo(),
        align="center",
        spacing="7",
    )


def index():
    return rx.container(content())


@rx.page(route="/wide")
def index_wide():
    return rx.container(content(), stack_children_full_width=True)


app = rx.App()
app.add_page(index)

Try out the / and /wide routes to see the difference for yourself.

Before

Screen.Recording.2024-04-17.at.14.38.51.mov

After

Note: this was before changing the buttons to not be stretched! Not the latest version of the PR or sample code.

Screen.Recording.2024-04-17.at.14.37.54.mov

masenf added 2 commits April 17, 2024 14:26
For certain block-level layout and form control elements, apply a default 100%
width to have a nicer look without reaching for CSS props.
Update default container props to look nicer out of the box without specifying
any CSS.
Copy link

linear bot commented Apr 17, 2024

Copy link
Contributor

@picklelo picklelo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the current implementation seems to mess up the counter app centering (though there's a text_align="center" within the center)

Screenshot 2024-04-17 at 5 29 09 PM

@Alek99
Copy link
Member

Alek99 commented Apr 18, 2024

Wow looks way better! Nice

masenf added 7 commits April 18, 2024 12:58
This can be applied to any component to ensure that its children vstack/hstack
and select children thereof have width=100% set for better out of the box
appearance in many situations.
This prop applies the STACK_CHILDREN_FULL_WIDTH css snippet to the container.
@masenf masenf force-pushed the masenf/vstack-default-layout branch from 67f2d43 to 0fb6bad Compare April 25, 2024 06:09
@@ -35,5 +35,11 @@ def create(cls, *children, **props):
else:
props["dangerouslySetInnerHTML"] = {"__html": children[0]}

# Apply the default classname
given_class_name = props.pop("class_name", [])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we apply to this all rx components in the future?

@picklelo picklelo merged commit b7e85ec into main Apr 30, 2024
46 checks passed
@masenf masenf deleted the masenf/vstack-default-layout branch June 25, 2024 02:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants