diff --git a/solara/autorouting.py b/solara/autorouting.py index e81bfcd68..9bb003e83 100644 --- a/solara/autorouting.py +++ b/solara/autorouting.py @@ -205,11 +205,12 @@ def get_args(f): content = solara.Markdown(path.read_text(), unsafe_solara_execute=True) main = solara.Div( + classes=["solara-autorouter-content"], children=[ solara.Title(route_current.label or "No title"), content, navigation, - ] + ], ) main = wrap_in_layouts(main, layouts) else: @@ -261,10 +262,11 @@ def get_args(f): Page = getattr(modules[route_current.path], "app", None) Page = getattr(modules[route_current.path], "page", Page) main = solara.Div( + classes=["solara-autorouter-content"], children=[ title_element, Page, - ] + ], ) main = wrap_in_layouts(main, layouts) elif Page is not None: @@ -276,10 +278,11 @@ def get_args(f): else: page = solara.Error(f"{Page} is not a component or element, but {type(Page)}") main = solara.Div( + classes=["solara-autorouter-content"], children=[ title_element, page, - ] + ], ) main = wrap_in_layouts(main, layouts) else: diff --git a/solara/components/applayout.py b/solara/components/applayout.py index eb32f06c8..034d6a046 100644 --- a/solara/components/applayout.py +++ b/solara/components/applayout.py @@ -306,7 +306,12 @@ def set_path(index): with v.Row(no_gutters=False, class_="solara-content-main"): v.Col(cols=12, children=children_content) else: - with v.Html(tag="div", style_="min-height: 100vh") as main: + # this limits the height of the app to the height of the screen + # and further down we use overflow: auto to add scrollbars to the main content + # the navigation drawer adds it own scrollbars + # NOTE: while developing this we added overflow: hidden, but this does not seem + # to be necessary anymore + with v.Html(tag="div", style_="height: 100vh") as main: with solara.HBox(): if use_drawer: with v.NavigationDrawer( @@ -340,8 +345,13 @@ def set_path(index): if fullscreen: solara.Button(icon_name="mdi-fullscreen-exit", on_click=lambda: set_fullscreen(False), icon=True, dark=False) - with v.Content(class_="solara-content-main"): - v.Col(cols=12, children=children_content) + with v.Content(class_="solara-content-main", style_="height: 100%;"): + # make sure the scrollbar does no go under the appbar by adding overflow: auto + # to a child of content, because content has padding-top: 64px (set by vuetify) + # the padding: 12px is needed for backward compatibility with the previously used + # v.Col which has this by default. If we do not use this, a solara.Column will + # use a margin: -12px which will make a horizontal scrollbar appear + solara.Div(style_="height: 100%; overflow: auto; padding: 12px;", children=children_content) if fullscreen: with v.Dialog(v_model=True, children=[], fullscreen=True, hide_overlay=True, persistent=True, no_click_animation=True) as dialog: v.Sheet(class_="overflow-y-auto overflow-x-auto", children=[main]) diff --git a/solara/components/columns.py b/solara/components/columns.py index eff4e177e..c5164e4cc 100644 --- a/solara/components/columns.py +++ b/solara/components/columns.py @@ -74,7 +74,10 @@ def Page(): style_flat = solara.util._flatten_style(style) with rv.Row(class_=class_, no_gutters=not gutters, dense=gutters_dense, style_=style_flat) as main: for child, width in zip(children, cycle(widths)): - with rv.Col(children=[child], style_=f"flex-grow: {width}; overflow: auto" if width != 0 else "flex-grow: 0;"): + # we add height: 100% because this will trigger a chain of height set if it is set on the parent + # via the style. If we do not set the height, it will have no effect. Furthermore, we only have + # a single child, so this cannot interfere with other siblings. + with rv.Col(children=[child], style_=f"height: 100%; flex-grow: {width}; overflow: auto" if width != 0 else "flex-grow: 0"): pass return main diff --git a/solara/server/assets/style.css b/solara/server/assets/style.css index 10ec9ed43..9045feacd 100644 --- a/solara/server/assets/style.css +++ b/solara/server/assets/style.css @@ -109,3 +109,7 @@ div.highlight { .solara-content-main { animation: solara-fade-in 0.5s ease; } + +.solara-autorouter-content { + height: 100%; +} diff --git a/solara/website/pages/apps/scrolling.py b/solara/website/pages/apps/scrolling.py new file mode 100644 index 000000000..a38cf95ee --- /dev/null +++ b/solara/website/pages/apps/scrolling.py @@ -0,0 +1,63 @@ +import solara + +github_url = solara.util.github_url(__file__) + +# list of nice soft colors +colors = [ + "#e6194b", + "#3cb44b", + "#ffe119", + "#4363d8", + "#f58231", + "#911eb4", +] + + +@solara.component +def Page1(): + with solara.Sidebar(): + solara.Button(label="View source", icon_name="mdi-github-circle", attributes={"href": github_url, "target": "_blank"}, text=True, outlined=True) + solara.Markdown("The sidebar will get scrollbars automatically, independently of the main content.") + for i in range(10): + with solara.Card(f"Card {i}"): + solara.Info(f"Text {i}", color=colors[i % len(colors)]) + + solara.Markdown("The main content will get scrollbars automatically, independently of the sidebar.") + for i in range(10): + with solara.Card(f"Card {i}"): + solara.Info(f"Text {i}", color=colors[i % len(colors)]) + + +@solara.component +def Page2(): + # it is important we do not interrupt the height 100% chain + limit_content_height = solara.use_reactive(True) + # warning: if we add "margin": "10px" to the style, we will trigger + # scrollbars in the parent div, if you really need to add margins, + # you should correct for that in the height using "calc(100% - 20px)" + with solara.Column(style={"height": "100%"}): + with solara.Sidebar(): + solara.Button(label="View source", icon_name="mdi-github-circle", attributes={"href": github_url, "target": "_blank"}, text=True, outlined=True) + solara.Markdown("Main content columns will scroll together, or independently if we limit height to 100%") + solara.Checkbox(label="Limit content height", value=limit_content_height) + + # if we do not limit height to 100%, solara's AppLayout will add a scroll bar + # to the main content, which will be independent of the sidebar's scroll bar + # but both columns will scroll together + with solara.Columns([2, 4], style={"height": "100%"} if limit_content_height.value else {}): + with solara.Card("I have my own scroll bar"): + solara.Markdown("") + for i in range(10): + with solara.Card(f"Card {i}"): + solara.Info(f"Text {i}", color=colors[i % len(colors)]) + with solara.Card("I also have my own scroll bar"): + solara.Markdown("") + for i in range(20): + with solara.Card(f"Card {i}"): + solara.Info(f"Text {i}", color=colors[i % len(colors)]) + + +routes = [ + solara.Route(path="/", component=Page1, label="Scrolling"), + solara.Route(path="custom", component=Page2, label="Custom scrolling"), +] diff --git a/solara/website/pages/examples/fullscreen/scrolling.py b/solara/website/pages/examples/fullscreen/scrolling.py new file mode 100644 index 000000000..2df8722b5 --- /dev/null +++ b/solara/website/pages/examples/fullscreen/scrolling.py @@ -0,0 +1,3 @@ +redirect = "/apps/scrolling" + +Page = True diff --git a/solara/website/public/examples/fullscreen/scrolling.png b/solara/website/public/examples/fullscreen/scrolling.png new file mode 100644 index 000000000..84718728f Binary files /dev/null and b/solara/website/public/examples/fullscreen/scrolling.png differ