Skip to content
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

[DataGridPro] autoHeight mode that fills the available height of the viewport #5451

Closed
2 tasks done
TheRealCuran opened this issue Jul 11, 2022 · 3 comments
Closed
2 tasks done
Labels
component: data grid This is the name of the generic UI component, not the React module!

Comments

@TheRealCuran
Copy link

Duplicates

  • I have searched the existing issues

Latest version

  • I have tested the latest version

Summary 💡

I would like to request an autoHeight mode, that automatically uses the available remaining height of the viewport and resizes the DataGridPro component to that value. The intended effect is, that no matter how the actual size of the viewport, DataGridPro fills it entirely, also reacting to resizing.

Examples 🌈

// more advanced properties like being able to define a bottom margin, etc. would be nice,
// but are not an absolute requirement, if I can set something like 98 %
<DataGridPro {...props} maxHeight={'98%'} maxWidth={'100%'} />

And this would lead to something like the following (sorry for the heavy redactions, but the data is unimportant and this is from an internal tool):
image

  • DataGridPro fills the whole height (and width here)
  • header column is pinned on scroll
  • scrolling is virtual and entirely within the DataGridPro component

For reference: currently I am doing something like the following.

  • _max-height-box.tsx
    // Another function component to get arround the hook limitations of React
    //
    // React – unlike Vue – is not allowing its class components access to Hooks.
    import * as React from 'react'
    import { Box, BoxProps, debounce } from '@mui/material'
    
    interface MaxHeightBoxProps extends BoxProps {
      id: string
      debounceTime?: number
      bottomMargin?: number
      minHeight?: number
    }
    
    export const MaxHeightBox: React.FunctionComponent<MaxHeightBoxProps> = (
      props,
    ) => {
      const MIN_HEIGHT = 400,
        BOTTOM_MARGIN = 5,
        DEFAULT_DEBOUNCE_TIME = 1000
    
      const calcCurHeight = () =>
        Math.max(
          props.minHeight ?? MIN_HEIGHT,
          window.visualViewport.height -
            (document.querySelector(`#${props.id}`)?.getBoundingClientRect().top ??
              0) -
            (props.bottomMargin ?? BOTTOM_MARGIN),
        )
    
      const [height, setHeight] = React.useState(calcCurHeight())
    
      const debouncedResizeHandler = debounce(() => {
        setHeight(calcCurHeight())
    
        return () => window.removeEventListener('resize', debouncedResizeHandler)
      }, props.debounceTime ?? DEFAULT_DEBOUNCE_TIME)
    
      React.useEffect(() => {
        window.addEventListener('resize', debouncedResizeHandler)
      })
    
      return (
        <Box {...props} sx={{ ...props.sx, width: '100%', height }}>
          {props.children}
        </Box>
      )
    }
  • MyDataGridUser.tsx
    export class MyDataGridUser extends React.PureComponent {
      import { MaxHeightBox } from './_max-height-box'
      
      render() {
        return (
          <MaxHeightBox id="data-grid-box" debounceTime={300}>
            <DataGridPro {...dataGridProProps} />
          </MaxHeightBox>
        )
      }
    }

Motivation 🔦

I have a DataGridPro that has a limited amount of elements, which doesn't want any pagination – all rows should always be available. DataGridPro has the property of pinning the head column row and adding scrolling, when it is placed within an element with a fixed height (side note: autoHeight disables the pinning, which is rather unfortunate). Since I do know how much space I will have on a user's screen or they might resize the window, I now have a custom <Box> wrapper, that listens to resize and always sets the height to the maximum available. That way I do get the scrolling of DataGridPro, the pinned header row and an responsive DataGridPro.

The motivation therefore is to get rid of this custom wrapper and have a mode for DataGridPro that automatically fills the whole available height and width (or whatever limit is passed to the DataGridPro component).

Side notes:

  • this also works around an update issue with the width changing, where the columns of DataGridPro wouldn't contract or expand properly, but get stuck on their last size.
  • Using flex yielded a two pixel height DataGridPro component, that didn't expand after loading the data from the server.

Order ID 💳 (optional)

47016

@TheRealCuran TheRealCuran added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Jul 11, 2022
@m4theushw
Copy link
Member

By default, the grid fits into the dimensions of its parent container. That being said, if the parent container fills the entire screen, the grid should follow it too. I didn't understand why this is not working for you. Could you provide a reproduction?

I created a demo to show how to make the grid fill the screen: https://codesandbox.io/s/datagridprodemo-demo-mui-x-forked-z9n1vm?file=/demo.tsx

Note that any element between html and the grid's parent should also fill the screen for it to work.

@m4theushw m4theushw added status: waiting for author Issue with insufficient information component: data grid This is the name of the generic UI component, not the React module! and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Jul 11, 2022
@TheRealCuran
Copy link
Author

OK, I must have messed up my boxes somewhere. In general the resizing by flex-box works, now that I've tried writing the repro. What is still an issue with a very basic setup is that columns "disappear", when I resize the window (both Firefox and Chrome, latest versions; also reproduced on different OS here). This is enough to trigger it, if you have withMuiV5DataGridApi feed the props dataGridApiRef, columns and rows (basically a HOC wrapper to be able to use the hooks for useGridApiRef and useDemoData).

  • index.tsx
    import * as React from 'react'
    import { createRoot } from 'react-dom/client'
    import { CssBaseline, GlobalStyles, ThemeProvider } from '@mui/material'
    import theme from './theme5' // really trivial, sets some colours
    import { ReproApp } from './repro-app'
    
    const container = document.querySelector('#reproApp')
    if (container === null) {
      throw new Error('Failed to find root element, this is not going to work')
    }
    
    const root = createRoot(container)
    root.render(
      <React.StrictMode>
        <ThemeProvider theme={theme}>
          <CssBaseline enableColorScheme={true} />
          <GlobalStyles
            styles={{
              'html, body': { height: '100%', margin: 0 },
              '#reproApp': {
                display: 'flex',
                flexDirection: 'column',
                height: '100%',
              },
            }}
          />
          <ReproApp />
        </ThemeProvider>
      </React.StrictMode>,
    )
  • repro-app.tsx
    /* eslint-env browser */
    import * as React from 'react'
    import {
      Box,
      Container,
      FormControl,
      InputLabel,
      MenuItem,
      Select,
      Snackbar,
      Typography,
    } from '@mui/material'
    import {
      DataGridPro,
      GridRowModel,
    } from '@mui/x-data-grid-pro'
    import {
      withMuiV5DataGridApi,
      WithMuiV5DataGridApiProps,
    } from '../_utils/with-muiv5-datagrid-api'
    
    interface ReproAppProps {
      foo?: string
    }
    
    class ReproAppImpl extends React.PureComponent<
      WithMuiV5DataGridApiProps<ReproAppProps>
    > {
      constructor(props: WithMuiV5DataGridApiProps<ReproAppProps>) {
        super(props)
      }
    
      render(): JSX.Element {
        const { dataGridApiRef, columns, rows } = this.props
        const selectVals = [] // usually comes from state
        return (
          <Container
            maxWidth={false}
            sx={{
              width: '100%',
              flex: 1,
              marginBottom: 5,
            }}
          >
            <Typography align="left" variant="h2">
              Some Title
            </Typography>
            <Box sx={{ marginBottom: '1em' }}>
              <FormControl>
                <InputLabel id="my-select-label">Some Select</InputLabel>
                <Select
                  displayEmpty={true}
                  autoWidth={true}
                  labelId="my-select-label"
                  id="my-select"
                  value={''}
                  label="Some Label"
                  onChange={(evt) => {
                    evt.preventDefault()
                    //
                  }}
                  renderValue={(val) => {
                    if (selectVals.length === 0) {
                      return <em>make selection</em>
                    }
    
                    let curVal = selectVals.find((iterVal) => iterVal.id === val)
    
                    return curVal?.label ?? <em>make selection</em>
                  }}
                >
                  <MenuItem disabled value="">
                    <em>make selection</em>
                  </MenuItem>
                  {selectVals.map((val) => (
                    <MenuItem key={`my-select-itm-${val.id}`} value={val.id}>
                      {val.label}
                    </MenuItem>
                  ))}
                </Select>
              </FormControl>
            </Box>
            <DataGridPro
              apiRef={dataGridApiRef}
              columns={columns}
              rows={rows}
              loading={rows.length === 0}
              rowHeight={100}
              pagination={false}
              hideFooter={true}
              // eslint-disable-next-line @typescript-eslint/no-magic-numbers
              pageSize={Math.max(rows.length, 100)}
              experimentalFeatures={{ newEditingApi: true }}
              processRowUpdate={(
                newRowData: GridRowModel<MyRowData>,
                oldRowData: GridRowModel<MyRowData>,
              ) => newRowData
              onProcessRowUpdateError={(error) => {
                let err_msg =
                  'Unknown error'
                if (error instanceof Error) {
                  err_msg = error.message
                }
                // triggers Snackbar usually
              }}
            />
            // Snackbar and dialog elements placed here
          </Container>
        )
      }
    }
    
    export const ReproApp = withMuiV5DataGridApi<ReproAppProps>(ReproAppImpl)

Once you made the window smaller with the "restore" button (from maximised) and go back to maximised you get a white area on the right:
image

Note, this only happens when the difference in width is large. If you drag to full screen size or the difference from the smaller window to the maximised size is "small enough", it will work out.

Anyway, I guess that is more of a bug and something different. For now I stick with the wrapper, that works reliably – no missing columns.

@github-actions github-actions bot removed the status: waiting for author Issue with insufficient information label Jul 12, 2022
@m4theushw
Copy link
Member

What is still an issue with a very basic setup is that columns "disappear", when I resize the window (both Firefox and Chrome, latest versions; also reproduced on different OS here).

We already had reports of that. You can follow #5085 and #5192

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: data grid This is the name of the generic UI component, not the React module!
Projects
None yet
Development

No branches or pull requests

2 participants