mui / mui-x

MUI X: Build complex and data-rich applications using a growing list of advanced React components, like the Data Grid, Date and Time Pickers, Charts, and more!
https://mui.com/x/
4.34k stars 1.3k forks source link

[DataGrid] - using renderHeader disables Data Grid's header cell description #14743

Closed sjobergjim closed 1 month ago

sjobergjim commented 1 month ago

Steps to reproduce

Link to live example

Steps:

  1. Open sandbox. It is a fork of your own sandbox found here
  2. Observe that the description is working (by hovering the header cell)
  3. Comment in row 21 (i.e. renderHeader: (params) => <div>I'm a header</div>,)
  4. Note that the description no longer works.

Current behavior

Whenever a custom header is rendered through the renderHeader-property in the columns for a DataGrid, the tooltip will stop working.

Expected behavior

I guess there are some options:

  1. TS-error stating that you cannot pass both description and renderHeader at the same time
  2. Rework internal logic so passing params to a custom header and spreading them into the container element of that component would make the description work (wrap result with a Tooltip component like my work-around below(?)).
  3. Add a CustomGridHeaderWrapper-component which could take care of this mapping. (If this already exists, I apologize because I couldn't find it!)

Context

I was simply trying to render a table where the headers only contained icons but also where the description would work on hover.

I've circumvented this issue by by wrapping the HeaderCell in a Tooltip component and passed the string that way instead. Code below is arguably not relevant but sharing in case it might help someone.

// column definition
const columns = useMemo((): ColumnDefinition[] => {
    return [
        {
            field: NAME,
            // ...
        },
        ...ICON_FIELDS.map(
            (field: Icon): ColumnDefinition => ({
                field,
                headerName: iconMapping[field].name(),
                renderHeader: ({ field }) => <HeaderCell field={field} />,
                renderCell: ({ value }) => <IconCell icon={value} />,
                valueGetter: (_, row) => (row.tags.includes(field) ? field : undefined),
                // description: iconMapping[field].name(), // does not work with custom header cells
                align: "center",
                headerAlign: "center",
                flex: 1,
            }),
        ),
    ];
}, [t]);

// HeaderCell
export const HeaderCell = ({ field }: HeaderCellProps) => {
    if (!isIcon(field)) return null;

    return <Tooltip title={iconMapping[field].name()}>{iconMapping[field].Icon}</Tooltip>;
};

// where
const iconMapping: Record<Icon, { Icon: JSX.Element; name: () => string }> = { ...

Your environment

Note: I did test with the latest versions of the below packages, having run pnpm update but I reverted those before running the @mui/envinfo here. I paid extra attention to the versions of the various MUI packages and noted that the base packages were locked at 5.16.7, wondered whether we had forgotten to bump them but then noticed in your own package.json that it matches your version!

npx @mui/envinfo ``` Don't forget to mention which browser you used. - Firefox Output from `npx @mui/envinfo` goes here. System: OS: Windows 10 10.0.19045 Binaries: Node: 21.5.0 - C:\Program Files\nodejs\node.EXE npm: 10.2.4 - C:\Program Files\nodejs\npm.CMD pnpm: 9.11.0 - ~\AppData\Local\pnpm\pnpm.CMD Browsers: Chrome: Not Found Edge: Chromium (127.0.2651.74) npmPackages: @emotion/react: ^11.13.3 => 11.13.3 @emotion/styled: ^11.13.0 => 11.13.0 @mui/icons-material: ^5.16.7 => 5.16.7 @mui/material: ^5.16.7 => 5.16.7 @mui/system: ^5.16.7 => 5.16.7 @mui/x-data-grid: ^7.14.0 => 7.14.0 @mui/x-data-grid-premium: ^7.14.0 => 7.14.0 @mui/x-date-pickers-pro: ^6.20.2 => 6.20.2 @types/react: ^18.3.4 => 18.3.4 react: ^18.3.1 => 18.3.1 react-dom: ^18.3.1 => 18.3.1 typescript: ^5.5.4 => 5.5.4 ```

Search keywords: DataGrid, renderHeader, description, tooltip, ColumnDefinition Order ID: 87236

k-rajat19 commented 1 month ago

Hi, I have refactored your sandbox to show how you can use description in renderHeader, you can mold it further according to your use case. Hope this helps

MBilalShafi commented 1 month ago

Adding to @k-rajat19's answer, the tooltip is rendered by an internal component which is replaced when renderHeader is used, in which case you have to handle it yourself.

Let me know if it resolves your concern.

sjobergjim commented 1 month ago

Thanks for quick replies! I appreciate the refactored example and pointing me to the source code of the GridColumnHeaderTitle component.

I'd say it's a bit counter-intuitive that the header's description isn't handled internally as a default even while passing a custom header component through renderHeader. However, with your help I made the following helper:

import { GridColumnHeaderParams, GridValidRowModel, useGridRootProps } from "@mui/x-data-grid-premium";
import { isOverflown } from "@mui/x-data-grid/utils/domUtils";
import { MouseEventHandler, ReactNode, useCallback, useRef, useState } from "react";

interface GridColumnHeaderTitleWrapperProps<R extends GridValidRowModel = GridValidRowModel, V = any, F = V> {
    children: ReactNode;
    props: GridColumnHeaderParams<R, V, F>;
}

export const GridColumnHeaderTitleWrapper = <R extends GridValidRowModel = GridValidRowModel, V = any, F = V>({
    children,
    props,
}: GridColumnHeaderTitleWrapperProps<R, V, F>) => {
    const { field, colDef } = props;
    const rootProps = useGridRootProps();
    const titleRef = useRef<HTMLDivElement>(null);
    const [tooltip, setTooltip] = useState("");

    const handleMouseOver = useCallback<MouseEventHandler<HTMLDivElement>>(() => {
        if (!colDef.description && titleRef?.current) {
            const isOver = isOverflown(titleRef.current);
            if (isOver) {
                setTooltip(field);
            } else {
                setTooltip("");
            }
        }
    }, [colDef.description, field]);

    return (
        <rootProps.slots.baseTooltip title={colDef.description || tooltip} {...rootProps.slotProps?.baseTooltip}>
            <div onMouseOver={handleMouseOver} ref={titleRef}>
                {children}
            </div>
        </rootProps.slots.baseTooltip>
    );
};

Maybe updating the docs here and exporting that wrapper from the library or something could be of help to you or others? (Although I believe you'd have to address the duplication of code between this helper and GridColumnHeaderTitle and I have no idea whether that'd go against your design philosophies etc, so it's meant as a suggestion!)

You can consider the issue resolved though, cheers! :+1:

MBilalShafi commented 1 month ago

Thank you for the update and for providing the code snippet 🙏 It should help other users looking for similar customization.

Regarding making it part of the component returned by renderHeader, I think it's a conscious choice not to do it because the component returned by renderHeader could be any component and not necessarily a text, and it might make sense not to have a tooltip at all for some custom implementations.

I'll go ahead and close this one since it resolved your issue, I'll be happy to reconsider making this a part of the application if a strong need arises.

github-actions[bot] commented 1 month ago

This issue has been closed. If you have a similar problem but not exactly the same, please open a new issue. Now, if you have additional information related to this issue or things that could help future readers, feel free to leave a comment.

[!NOTE] We value your feedback @sjobergjim! How was your experience with our support team? We'd love to hear your thoughts in this brief Support Satisfaction survey. Your insights help us improve!