add TableOfContents#513
Conversation
deadkex
commented
Feb 15, 2025

|
Hi @deadkex |
|
@AnnMarieW it is a draft since i am looking for ways to implement it like in the Mantine docs (for example turn the TableOfContents elements into Anchors so the path updates) |
|
@AnnMarieW i'm open for ideas |
|
Try this: |
|
Here's an example with a multi-page app. The table of content doesn't update when you change pages - unless the page is refreshed. Probably need to set initial data? https://mantine.dev/core/table-of-contents/#initial-data import dash
from dash import Dash, _dash_renderer, html
import dash_mantine_components as dmc
_dash_renderer._set_react_version("18.2.0") # noqa
app = Dash(external_stylesheets=dmc.styles.ALL, use_pages=True, pages_folder="")
def make_section(i, page):
return dmc.Box(
[dmc.Title(children=f"{page} Title {i}", id=str(i), order=2), dmc.Space(h=300)],
p="lg",
)
aside = dmc.AppShellAside(
children=dmc.ScrollArea(
children=dmc.Stack(
children=[
dmc.Space(h=50),
dmc.Text("Table of contents", fw=500),
dmc.TableOfContents(
variant="filled",
color="blue",
size="sm",
radius="sm",
),
]
),
type="never",
),
px="lg",
)
dash.register_page(
"home",
path="/",
layout=html.Div([make_section(i, "home") for i in range(10)] + [aside]),
)
dash.register_page(
"page1",
path="/page-1",
layout=html.Div([make_section(i, "page1") for i in range(10)] + [aside]),
)
app.layout = dmc.MantineProvider(
forceColorScheme="light",
children=dmc.AppShell(
[
dmc.AppShellNavbar(
[
dmc.NavLink(label="Home", href="/"),
dmc.NavLink(label="Page 1", href="/page-1"),
]
),
dmc.AppShellMain(dash.page_container),
],
navbar={"width": 200},
),
)
if __name__ == "__main__":
app.run(debug=True)
|
|
I can't work on this currently- if someone wants to pick it up feel free to give it a go |
# Conflicts: # src/ts/index.ts
|
@AnnMarieW i fixed the problems, can you please confirm that it works as intended? |
|
Looks like it now works to click on a toc item and it scrolls into view. However, it doesn't work with multi-page apps. Probably need to implement the reinitialize https://mantine.dev/core/table-of-contents/#reinitialize |
|
@AnnMarieW Can you give me a MRE please? |
|
@AnnMarieW What do you think about a refresh prop? main.py import random
import dash
from dash import Dash, Output, State, Input, dcc, clientside_callback
import dash_mantine_components as dmc
app = Dash(external_stylesheets=dmc.styles.ALL, use_pages=True)
app.layout = dmc.MantineProvider(
forceColorScheme="dark",
children=dmc.AppShell([
dcc.Location(id="url"),
dmc.AppShellMain(
children=dmc.Stack([
dmc.Stack([dmc.Anchor(x, href=x) for x in ["/", "/a", "/b"]]),
dash.page_container
])
)
])
)
if __name__ == "__main__":
app.run(debug=True)pages/a.py import random
import dash
import dash_mantine_components as dmc
dash.register_page(__name__)
def layout():
items = [
dmc.Title(children="Content", id="content", order=1),
dmc.Space(h=300)
]
for i in range(20):
items.append(dmc.Title(children=f'Title {i}', id=str(i), order=random.randint(1, 6)))
items.append(dmc.Space(h=300))
return [
dmc.AppShellAside(
children=dmc.ScrollArea(
children=dmc.Stack(
children=[
dmc.Space(h=50),
dmc.Text("Table of contents", fw=500),
dmc.TableOfContents(
variant="filled",
size="md",
scrollIntoViewOptions={ "behavior": "smooth" }
)
]),
type='never'),
px="5%",
withBorder=False),
dmc.Box(
px="20%",
children=[
*items,
dmc.Space(h=1000),
]
)
]pages/b.py import random
import dash
from dash import callback, Output, Input
import dash_mantine_components as dmc
dash.register_page(__name__)
def layout():
return [
dmc.AppShellAside(
children=dmc.ScrollArea(
children=dmc.Stack(
children=[
dmc.Space(h=50),
dmc.Text("Table of contents", fw=500),
dmc.TableOfContents(
id="toc",
variant="none",
size="md",
scrollIntoViewOptions={"behavior": "smooth"}
)
]),
type='never'),
px="5%",
withBorder=False),
dmc.Button("test", id="button"),
dmc.Box(
px="20%",
id="test"
)
]
@callback(
Output("test", "children"),
Output("toc", "refresh"),
Input("url", "pathname"),
Input("button", "n_clicks")
)
def create_content(_, n_clicks):
items = [
dmc.Title(children="Content B", id="content", order=1),
dmc.Space(h=300)
]
for i in range(20):
items.append(dmc.Title(children=f'Title {i}', id=str(i), order=random.randint(1, 6)))
items.append(dmc.Space(h=300))
return [
*items,
dmc.Space(h=1000),
], n_clicks |
… refreshing upon necessary updates
|
I have a PR on your fork against this branch. Check it out and merge if everything is good. |
adjustments and utilization of functions similar to `dcc.Loading` for refreshing upon necessary updates
|
@deadkex Thanks for coming back to this PR and working on this component again -- things are looking good! @BSd3v Thanks for adding the feature to reinitialize the TOC automatically by using the dash loading status. It's nice to be able to do that without using a callback. I'm going to work on adding tests and docs. That's usually a good way to make sure all the features are working correctly. So far, I have one additional comment: The |
|
@BSd3v Your feature to to support the loading state is only compatible with dash>=3. Is it possible to make it work with dash 2 as well? If not, how about adding a check for the dash version and an error message to use the |
|
What do you think about changing the prop |
|
@BSd3v - would you like to check out the changes we discussed for handling the target id and the loading completed function for dash 3? |
| /** Data used to render content until actual values are retrieved from the DOM, empty array by default */ | ||
| initialData?: InitialTableOfContentsData[]; |
There was a problem hiding this comment.
Do you think we should include the initialData prop? It refers to server-side rendering, which is not applicable in Dash
TableOfContents retrieves data on mount. If you want to render headings before TableOfContents component is mounted (for example during server-side rendering), you can pass initialData prop with array of headings data. initialData is replaced with actual data on mount.
There was a problem hiding this comment.
Yeah unless we can demonstrate a good use for it, sounds like we should drop it. I could imagine this content showing up while the rest of the page is loading, but then it would either be wrong or it would not respond properly to clicking on the headings until the page finishes loading and the real content is present.
Co-authored-by: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com>
Co-authored-by: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com>
Co-authored-by: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com>
Co-authored-by: Ann Marie Ward <72614349+AnnMarieW@users.noreply.github.com>
Removed commented-out code for scrollSpyOptions.
|
@alexcjohnson Do you have time to take a look at this PR? This component wasn't a simple one to add and your feedback is always appreciated. |
|
Cool component! Looks good, @AnnMarieW I agree about |
Co-authored-by: Alex Johnson <alex@plot.ly>
|
Thanks so much for your quick review! 🙏 |
|
Thanks @deadkex for seeing this over the finish line! Thanks for your feedback and reviews @BSd3v and @alexcjohnson This will be an fantastic new feature for the next release 🎉 💃 |