Skip to content

NavLink Update for Active#504

Merged
AnnMarieW merged 14 commits intosnehilvj:masterfrom
BSd3v:navlink-update-active
Feb 25, 2025
Merged

NavLink Update for Active#504
AnnMarieW merged 14 commits intosnehilvj:masterfrom
BSd3v:navlink-update-active

Conversation

@BSd3v
Copy link
Copy Markdown
Contributor

@BSd3v BSd3v commented Feb 5, 2025

adding support for NavLink to set active based upon page navigation automatically:

  • options for active are now boolean | 'partial' | 'exact'
  • partial will care if the pathname starts with the href
  • exact will display if the pathname is an exact match
  • true / false will disable the matching and set true and false respectively

closes #365

BSd3v added 3 commits February 5, 2025 12:06
…utomatically:

- options for `active` are now `boolean | 'partial' | 'exact'`
- partial will care if the pathname starts with the href
- exact will display if the pathname is an exact match
- true / false will disable the matching and set true and false respectively
@AnnMarieW
Copy link
Copy Markdown
Collaborator

AnnMarieW commented Feb 9, 2025

Thanks @BSd3v

This works great! Will target merging after dmc V1 is released (and after dash 3 is released).

To do:

  • Updates for V1
  • Changelog
  • docs
  • update active docstring
  • handle hash or query strings?

Docstrings

I think the dosctring could be a little more clear. Perhaps something like:

Apply 'active' style to the link. Set to "exact" to automatically toggle the active status when the current pathname matches href, or to "partial" to automatically toggle when the pathname starts with the href.

I like the example included in the dbc docstring, but it would be better to include it in the docs instead:

For example
- dbc.NavLink(..., href="/my-page", active="exact") will be active on
"/my-page" but not "/my-page/other" or "/random"
- dbc.NavLink(..., href="/my-page", active="partial") will be active on
"/my-page" and "/my-page/other" but not "/random"

Handle hash or query strings

Do you think there is a way to accommodate a link where the href includes a # or a query string?
For example:

dmc.NavLink(label="page 1", href="/page-1#subject-1", active="exact"),

Example App

Here's a good sample app for the release announcement and the docs. This demos nested links using the active="partial" and active="exact" to keep the parent link showing active when any of the child links are active.

image

image

import dash
import dash_mantine_components as dmc
from dash import Dash, _dash_renderer, html
_dash_renderer._set_react_version("18.2.0")

app = Dash(external_stylesheets=dmc.styles.ALL, use_pages=True, pages_folder="")

dash.register_page("home", path="/", layout=html.Div("I'm home"))
dash.register_page("page1", path="/page-1", layout=html.Div("Info about page 1 subjects"))
dash.register_page("page1s1", path="/page-1/sub-1", layout=html.Div("page 1 subject 1"))
dash.register_page("page1s2", path="/page-1/sub-2", layout=html.Div("page 1 subject 2"))

component = dmc.Box([
    dmc.NavLink(label="home", href="/", active="exact"),
    dmc.NavLink(
            label="Page 1",
            childrenOffset=28,
            href="/page-1",
            active="partial",
            children=[
                dmc.NavLink(label="Subject 1", href="/page-1/sub-1", active="exact"),
                dmc.NavLink(label="Subject 2", href="/page-1/sub-2", active="exact"),
            ],
    ),
    dmc.Divider(mb="lg"),
    dash.page_container
])


app.layout = dmc.MantineProvider([component])

if __name__ == "__main__":
    app.run(debug=True)

Comment thread src/ts/components/core/NavLink.tsx Outdated
Comment on lines +76 to +92
const pathnameToActive = pathname => {
setLinkActive(
active === true ||
(active === 'exact' && pathname === href) ||
(active === 'partial' && pathname.startsWith(href)) ||
(active === 'hyperlink' && pathname.endsWith(href))
);
};

const parsePath = (location) => {
if (active === 'hyperlink') {
pathnameToActive(location.href)
}
else {
pathnameToActive(location.pathname)
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I like the endsWith option so it can pick up query stings and # for scrolling to a position on a page. What do you think about using different variable names to make it more clear?

    setLinkActive(
        active === true ||
        (active === 'exact-path' && location.pathname === href) ||  
        (active === 'starts-with-path' && location.pathname.startsWith(href)) ||  
        (active === 'ends-with-url' && fullUrl.endsWith(href)) 
    );
    ```

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, I think that would be fine.

Was more taking the lead from dbc on it.

@AnnMarieW
Copy link
Copy Markdown
Collaborator

Hey @alexcjohnson
This PR still needs to be updated for V1, but what do you think about the concept and prop names - especially the option for matching the href that includes hash tags and query strings.

I tried it out in the dmc docs and it works great. Eliminates the need to do the clientside callback to highlight the active links.

@alexcjohnson
Copy link
Copy Markdown
Collaborator

I like it! It's going a bit beyond just wrapping Mantine, but it seems like it's for a good cause.

Be careful about the logic though - just startsWith and endsWith would give strange results, like if you have two top-level path parts where one word is the beginning of the other - a word and its plural for example but there are plenty of otherwise unrelated word pairs that would do this. I think we'd want starts-with-path to only match if either the path is exact or the next character after the match is a /. ends-with-url I guess if people always start their href at either ? or # it doesn't have that same danger, so we could opt to just document that as a recommendation but if someone wants to ignore that we leave it up to them. I might also suggest a flavor for exact including query params and hash. Not full URL, I think we still want to start with the path, so the naming is tricky, but maybe exact-path-plus?

@BSd3v
Copy link
Copy Markdown
Contributor Author

BSd3v commented Feb 17, 2025

@alexcjohnson,

The thinking behind the endsWith is that this could be used to highlight links within the document as they are activated or clicked on. Similar to how this is done here:

https://www.ag-grid.com/javascript-data-grid/grid-events/#reference-miscellaneous

If you have to match the exact href, the links become much more of a pain.

We could match an exact-search? to be an exact href. Especially if someone was to use layouts in this fashion. 😄

@alexcjohnson
Copy link
Copy Markdown
Collaborator

I wasn’t suggesting removing ends-with-url, just being clear on how to use it to avoid unexpected behavior, ie recommending always starting with # or ?, or I guess /.

@AnnMarieW
Copy link
Copy Markdown
Collaborator

Reverted to include just active="exact" and active="partial"

This now matches the functionality and prop names in the dash-bootstrap-components library. It's simpler and will be easier to maintain and document.

Draft of the docs are here: snehilvj/dmc-docs#165

Comment thread src/ts/components/core/NavLink.tsx Outdated
setLinkActive(
active === true ||
(active === 'exact' && pathname === href) ||
(active === 'partial' && pathname.startsWith(href))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I still think startsWith is going to lead to unintended consequences, and partial should instead use

pathname === href || pathname.startsWith(href + '/')

To account for paths like:

/al
/al/subpage
/alex
/alex/subpage

If you use just startsWith, then href='/al' would match all of these, but you really only want to match the first two.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if you do this

pathname === href || pathname.startsWith(href + '/')

and links looks like:

 dmc.NavLink(label="al", href="/al", active='partial'),
 dmc.NavLink(label="al2", href="/al/2", active='partial'),

Then if you click on the first link neither is active and when you click on the second link then only the /al page is active.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@BSd3v suggested this and it works well 🙂

   (active === 'partial' && (pathname.startsWith(href + '/') || pathname == href))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

oh, I see that's what you had too @alexcjohnson 😊 I was just fixated on the href + '/' part and missed it.

Copy link
Copy Markdown
Collaborator

@alexcjohnson alexcjohnson left a comment

Choose a reason for hiding this comment

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

💃

@AnnMarieW AnnMarieW merged commit 3f47d76 into snehilvj:master Feb 25, 2025
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.

[Feature Request] Add active prop to NavLink

3 participants