Skip to content

allow functions as props in renderNode in Tree component #608

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

Merged
merged 4 commits into from
Jun 30, 2025

Conversation

AnnMarieW
Copy link
Collaborator

@AnnMarieW AnnMarieW commented Jun 24, 2025

This PR enables custom rendering of the labels in the Tree component

  • adds the renderNode prop and allows for functions to be passed like {"function": "myLeaf"} where myLeaf is a JavaScript function defined in the assets folder.
  • If renderNode is not provided, then the default rendering is used.

To do:

Sample app based on the example in the Mantine docs:

image

import dash_mantine_components as dmc
import dash_iconify
from dash import Dash



data = [
    {
        "label": "src",
        "value": "src",
        "children": [
            {
                "label": "components",
                "value": "src/components",
                "children": [
                    {"label": "Accordion.tsx", "value": "src/components/Accordion.tsx"},
                    {"label": "Tree.tsx", "value": "src/components/Tree.tsx"},
                    {"label": "Button.tsx", "value": "src/components/Button.tsx"},
                ],
            },
        ],
    },
    {
        "label": "node_modules",
        "value": "node_modules",
        "children": [
            {
                "label": "react",
                "value": "node_modules/react",
                "children": [
                    {"label": "index.d.ts", "value": "node_modules/react/index.d.ts"},
                    {
                        "label": "package.json",
                        "value": "node_modules/react/package.json",
                    },
                ],
            },
            {
                "label": "@mantine",
                "value": "node_modules/@mantine",
                "children": [
                    {
                        "label": "core",
                        "value": "node_modules/@mantine/core",
                        "children": [
                            {
                                "label": "index.d.ts",
                                "value": "node_modules/@mantine/core/index.d.ts",
                            },
                            {
                                "label": "package.json",
                                "value": "node_modules/@mantine/core/package.json",
                            },
                        ],
                    },
                    {
                        "label": "hooks",
                        "value": "node_modules/@mantine/hooks",
                        "children": [
                            {
                                "label": "index.d.ts",
                                "value": "node_modules/@mantine/hooks/index.d.ts",
                            },
                            {
                                "label": "package.json",
                                "value": "node_modules/@mantine/hooks/package.json",
                            },
                        ],
                    },
                    {
                        "label": "form",
                        "value": "node_modules/@mantine/form",
                        "children": [
                            {
                                "label": "index.d.ts",
                                "value": "node_modules/@mantine/form/index.d.ts",
                            },
                            {
                                "label": "package.json",
                                "value": "node_modules/@mantine/form/package.json",
                            },
                        ],
                    },
                ],
            },
        ],
    },
    {
        "label": "package.json",
        "value": "package.json",
    },
    {
        "label": "tsconfig.json",
        "value": "tsconfig.json",
    },
]





app = Dash()

component = dmc.Tree(data=data, renderNode={"function": "myLeaf"}, m="lg")


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

if __name__ == "__main__":
    app.run(debug=True)
var dmcfuncs = window.dashMantineFunctions = window.dashMantineFunctions || {};

dmcfuncs.myLeaf = function (payload) {
  const dmc = window.dash_mantine_components;
  const iconify = window.dash_iconify;

  function getIcon(name, isFolder, expanded) {
    const size = 14;

    if (name.endsWith('package.json')) {
      return React.createElement(iconify.DashIconify, {
        icon: 'logos:npm-icon',
        width: size,
        height: size
      });
    }

    if (
      name.endsWith('.ts') ||
      name.endsWith('.tsx') ||
      name.endsWith('tsconfig.json')
    ) {
      return React.createElement(iconify.DashIconify, {
        icon: 'logos:typescript-icon',
        width: size,
        height: size
      });
    }

    if (name.endsWith('.css')) {
      return React.createElement(iconify.DashIconify, {
        icon: 'vscode-icons:file-type-css',
        width: size,
        height: size
      });
    }

    if (isFolder) {
      return React.createElement(iconify.DashIconify, {
        icon: expanded ? 'tabler:folder-open' : 'tabler:folder',
        width: size,
        height: size,
        color: '#f59f00' // Mantine yellow-9
      });
    }

    return null;
  }

  const { node, expanded, hasChildren, elementProps } = payload;

  return React.createElement(
    dmc.Group,
    { key: payload.node.value, gap: 5, ...elementProps },
    getIcon(node.value, hasChildren, expanded),
    React.createElement('span', null, node.label)
  );
};

Copy link

Test Environment for snehilvj/dash-mantine-components-608
Updated on: 2025-06-24 22:00:14 UTC

@AnnMarieW
Copy link
Collaborator Author

Hey @Godisemo - I thought you might be interested in this one. I'd love to get your feedback!

Here is the sample app deployed on PyCafe

@AnnMarieW AnnMarieW marked this pull request as ready for review June 25, 2025 21:38
@AnnMarieW AnnMarieW requested a review from alexcjohnson June 25, 2025 21:38
@Godisemo
Copy link
Contributor

Hey @Godisemo - I thought you might be interested in this one. I'd love to get your feedback!

Here is the sample app deployed on PyCafe

This looks like a great addition @AnnMarieW. My only question is how would it work if one makes a custom node renderer with some custom checkboxes, would it still be possible to connect the state to the tree checked property?

@AnnMarieW
Copy link
Collaborator Author

AnnMarieW commented Jun 26, 2025

@Godisemo

Great question! I works well. As mentioned in the Mantine docs, it's necessary to use the CheckboxIndicator component. The checked state is still controlled by the Tree and passed to the renderNode function where it's displayed.

Here is an example (which I'll also add to the docs)

Uses the same data as above

import json
import dash_mantine_components as dmc
from dash import Dash,  callback, Input, Output
import dash_iconify

app = Dash()

component = dmc.Stack([
    dmc.Tree(
        data=data,
        levelOffset=23,
        expandOnClick=False,
        renderNode={"function": "myLeafCheckbox"},
        id="tree-checkboxes" ),
    dmc.CodeHighlight(id="checked-nodes", code="", language="json"),
])

app.layout = dmc.MantineProvider(
    component
)

@callback(
    Output("checked-nodes", "code"),
    Input("tree-checkboxes", "checked")
)
def update(checked):
    return  json.dumps( checked, indent=4)

if __name__ == "__main__":
    app.run(debug=True)
var dmcfuncs = window.dashMantineFunctions = window.dashMantineFunctions || {};

dmcfuncs.myLeafCheckbox = function (payload) {
  const React = window.React;
  const dmc = window.dash_mantine_components;
  const iconify = window.dash_iconify;

  const { node, expanded, hasChildren, elementProps, tree } = payload;

  const checked = tree.isNodeChecked(node.value);
  const indeterminate = tree.isNodeIndeterminate(node.value);

  return React.createElement(
    dmc.Group,
    { key: node.value, gap: "xs", ...elementProps },
    [
      React.createElement(dmc.CheckboxIndicator, {
        key: "checkbox",
        checked,
        indeterminate,
        onClick: (e) => {
          e.stopPropagation();
          if (checked) {
            tree.uncheckNode(node.value);
          } else {
            tree.checkNode(node.value);
          }
        },
      }),
      React.createElement(
        dmc.Group,
        {
          key: "label-group",
          gap: 5,
          onClick: () => tree.toggleExpanded(node.value),
        },
        [
          React.createElement("span", { key: "label" }, node.label),
          hasChildren &&
            React.createElement(iconify.DashIconify, {
              key: "icon",
              icon: "tabler:chevron-down",
              width: 14,
              style: {
                transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
                transition: "transform 0.2s ease",
              },
            }),
        ]
      ),
    ]
  );
};

image

Copy link
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.

💃 Nice use of emoji in the test 🍃

@AnnMarieW AnnMarieW merged commit e52de80 into snehilvj:master Jun 30, 2025
1 check passed
@AnnMarieW AnnMarieW deleted the add-renderNode-to-Tree branch June 30, 2025 18:14
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