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

fix(useGridLayout): Support header groups #3542

Merged
merged 1 commit into from
Apr 20, 2022

Conversation

CollinsSpencer
Copy link

Header Groups Bug

Resolves #3537

useGridLayout could not display header groups by default. This seemed like a bug as it is fairly common functionality that it should be able to handle.

Header groups can be supported with the grid layout by using the css grid-column property with a span of the column's totalVisibleHeaderCount.

Column Resizing Bug

useGridLayout has a bug that comes from 'auto' or 'fr' units. When some resizing columns, the cursor doesn't stay with the resizer handle because 'auto' width columns rescale based on the column being resized. e.g. columnResizing state thinks the column width is 150, but it is actually 200 because of grid 'auto' resizing.

This column resizing bug was fixed by locking the width of all columns not being resized. Now, while resizing, this behaves exactly like the Resizing example (with useBlockLayout). After resizing, all columns that have not been resized will default back to their previous value. As a bonus, this supports user-provided 'width' values on columns. If 'auto' width columns is desired (instead of the default 150), that can be set with the defaultColumn useTable prop.

useGridLayout also had a bug where columns could be resized beyond the minWidth and maxWidth values. These values are now taken into account when resizing. However, this MR does not apply minWidth and maxWidth during initial column sizing.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
@vercel
Copy link

vercel bot commented Nov 14, 2021

Someone is attempting to deploy a commit to the Tanstack Team on Vercel.

A member of the Team first needs to authorize it.

@CollinsSpencer
Copy link
Author

Column maxWidth could be supported with minmax, which has good browser support https://caniuse.com/?search=minmax.

// getTableProps
const width = typeof column.width === 'number' ? `${column.width}px` : column.width
const maxWidth = typeof column.maxWidth === 'number' ? `${column.maxWidth}px` : column.maxWidth
return `minmax(${width},${maxWidth})`
// instead of
if (typeof column.width === 'number') return `${column.width}px`
return column.width

However, I'm not sure if we could support both minWidth and maxWidth at the same time. There doesn't appear to be something like clamp for css grid. Unfortunately you can't do nested minmax functions. If each of the values were numeric, then we could do a min inside of the minmax. However, that removes a lot of the value of using css grid.

@CollinsSpencer
Copy link
Author

Adapted Examples

(since there aren't any examples with useGridLayout)

Column Resizing Example

const Styles = styled.div`
  padding: 1rem;

  .table {
    display: inline-block;
    border-spacing: 0;
    border: 1px solid black;

    .th,
    .td {
      margin: 0;
      padding: 0.5rem;
      border-bottom: 1px solid black;
      border-right: 1px solid black;

      ${'' /* One way to deal with borders in the useGridLayout table
              is to use nth-child pseudo-selectors. */}
      :nth-child(6n + 8) {
        border-right: 0;
      }
      :nth-child(6n + 8):nth-last-child(-n + 7) ~ * {
        border-bottom: 0;
      }

      .resizer {
        display: inline-block;
        background: blue;
        width: 10px;
        height: 100%;
        position: absolute;
        right: 0;
        top: 0;
        transform: translateX(50%);
        z-index: 1;
        ${'' /* prevents from scrolling while dragging on touch devices */}
        touch-action:none;

        &.isResizing {
          background: red;
        }
      }
    }
  }
`

function Table({ columns, data }) {
  const defaultColumn = React.useMemo(
    () => ({
      minWidth: 30,
      width: 'auto',
      maxWidth: 400,
    }),
    []
  )

  const {
    getTableProps,
    headerGroups,
    rows,
    prepareRow,
    state,
    resetResizing,
  } = useTable(
    {
      columns,
      data,
      defaultColumn,
    },
    useGridLayout,
    useResizeColumns
  )

  return (
    <>
      <button onClick={resetResizing}>Reset Resizing</button>
      <div>
        <div {...getTableProps()} className="table">
          {headerGroups.map(headerGroup =>
            headerGroup.headers.map(column => (
              <div {...column.getHeaderProps()} className="th">
                {column.render('Header')}
                {/* Use column.getResizerProps to hook up the events correctly */}
                <div
                  {...column.getResizerProps()}
                  className={`resizer ${column.isResizing ? 'isResizing' : ''}`}
                />
              </div>
            ))
          )}

          {rows.map((row, i) => {
            prepareRow(row)
            return row.cells.map(cell => {
              return (
                <div {...cell.getCellProps()} className="td">
                  {cell.render('Cell')}
                </div>
              )
            })
          })}
        </div>
      </div>
      <pre>
        <code>{JSON.stringify(state, null, 2)}</code>
      </pre>
    </>
  )
}

Expanding Example

const Styles = styled.div`
  padding: 1rem;

  table {
    border-spacing: 0;
    border: 1px solid black;

    ${'' /* One way to deal with borders in the useGridLayout table
              is to use grid-gap and background. */}
    background-color: black;
    grid-gap: 1px;
    > * {
      background-color: white;
    }

    th,
    td {
      margin: 0;
      padding: 0.5rem;
    }
  }
`

function Table({ columns: userColumns, data }) {
  const {
    getTableProps,
    headerGroups,
    rows,
    prepareRow,
    state: { expanded },
  } = useTable(
    {
      columns: userColumns,
      data,
    },
    useExpanded, // Use the useExpanded plugin hook
    useGridLayout
  )

  return (
    <>
      <table {...getTableProps()}>
        {headerGroups.map(headerGroup =>
          headerGroup.headers.map(column => (
            <th {...column.getHeaderProps()}>{column.render('Header')}</th>
          ))
        )}
        {rows.map((row, i) => {
          prepareRow(row)
          return row.cells.map(cell => {
            return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
          })
        })}
      </table>
      <br />
      <div>Showing the first 20 results of {rows.length} rows</div>
      <pre>
        <code>{JSON.stringify({ expanded: expanded }, null, 2)}</code>
      </pre>
    </>
  )
}

Sub-Components Example

const Styles = styled.div`
  padding: 1rem;

  .table {
    border-spacing: 0;
    border: 1px solid black;

    ${'' /* One way to deal with borders in the useGridLayout table
              is to use grid-gap and background. */}
    background-color: black;
    grid-gap: 1px;
    > * {
      background-color: white;
    }

    > div {
      margin: 0;
      padding: 0.5rem;
    }
  }
`

// A simple way to support a renderRowSubComponent is to make a render prop
// This is NOT part of the React Table API, it's merely a rendering
// option we are creating for ourselves in our table renderer
function Table({ columns: userColumns, data, renderRowSubComponent }) {
  const {
    getTableProps,
    headerGroups,
    rows,
    prepareRow,
    state: { expanded },
  } = useTable(
    {
      columns: userColumns,
      data,
    },
    useExpanded, // We can useExpanded to track the expanded state
    // for sub components too!
    useGridLayout
  )

  return (
    <>
      <pre>
        <code>{JSON.stringify({ expanded: expanded }, null, 2)}</code>
      </pre>
      <div className="table" {...getTableProps()}>
        {headerGroups.map(headerGroup =>
          headerGroup.headers.map(column => (
            <div {...column.getHeaderProps()}>
              {column.render('Header')}
            </div>
          ))
        )}
        {rows.map((row, i) => {
          prepareRow(row)
          return (
            <>
              {row.cells.map(cell => {
                return <div {...cell.getCellProps()}>{cell.render('Cell')}</div>
              })}
              {/*
                If the row is in an expanded state, render a row with a
                column that fills the entire length of the table.
              */}
              {row.isExpanded ? (
                <div {...row.getRowProps()}>
                  {/*
                    Inside it, call our renderRowSubComponent function. In reality,
                    you could pass whatever you want as props to
                    a component like this, including the entire
                    table instance. But for this example, we'll just
                    pass the row
                  */}
                  {renderRowSubComponent({ row })}
                </div>
              ) : null}
            </>
          )
        })}
      </div>
      <br />
      <div>Showing the first 20 results of {rows.length} rows</div>
    </>
  )
}

@github-actions
Copy link

github-actions bot commented Apr 8, 2022

This pull request is being marked as stale (no activity in the last 14 days)

@github-actions github-actions bot added the Stale label Apr 8, 2022
@github-actions
Copy link

github-actions bot commented Apr 8, 2022

This pull request has been detected as stale (no activity in the last 14 days) and automatically closed. It's very likely that your pull request has remained here this long because it introduces breaking changes to the v7 API. React Table v8 is currently in alpha (soon beta!) and already contains bug fixes, performance improvements and architectural changes that likely address the issue this pull request is meant to solve.

  • If your v7 pull request has been previously labeled as having breaking changes, please try the new v8 alpha/beta releases
  • If your v7 pull request has not already been labeled as a breaking change, open a new pull request.
  • If this was a v8 pull request and was closed by mistake, please reopen or leave a comment below.

@tannerlinsley
Copy link
Collaborator

Was this closed by mistake?

@CollinsSpencer
Copy link
Author

@tannerlinsley This PR is for v7 and makes useGridLayout production ready, with all the same features as the other layout hooks, where header groups display correctly and column resizing works.

It's unclear to me if updates will still be made to v7 going forward, but I can open a new PR to the v7 branch if you think this is worthwhile.

@tannerlinsley tannerlinsley reopened this Apr 10, 2022
@tannerlinsley
Copy link
Collaborator

Reopened

@CollinsSpencer CollinsSpencer changed the base branch from alpha to v7 April 10, 2022 23:40
@tannerlinsley tannerlinsley merged commit 682ac0a into TanStack:v7 Apr 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

useGridLayout does not support header groups
2 participants