Skip to content

Commit

Permalink
Grid active cell logic and implement basic table functions
Browse files Browse the repository at this point in the history
  • Loading branch information
markus-moser committed Dec 13, 2024
1 parent 067bf1d commit 468812e
Show file tree
Hide file tree
Showing 11 changed files with 488 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export const useStyle = createStyles(({ token, css }) => {
display: flex;
width: 100%;
height: 100%;
&.default-cell--active:not(:focus):not(.default-cell--edit-mode) {
background-color: ${token.controlItemBgActive};
}
&:focus {
outline: 1px solid ${token.colorPrimaryActive};
outline-offset: -1px;
Expand Down
11 changes: 10 additions & 1 deletion assets/js/src/core/components/grid/columns/default-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ export const DefaultCell = ({ ...props }: DefaultCellProps): React.JSX.Element =
// @todo move to new dynamic type system
// onCopy={ onCopy }
onDoubleClick={ onDoubleClick }
onFocus={ () => props.onFocus?.({
rowIndex: row.index,
columnIndex: column.getIndex(),
columnId: column.id
}) }
onKeyDown={ onKeyDown }
// @todo move to new dynamic type system
// onPaste={ onPaste }
Expand All @@ -81,11 +86,15 @@ export const DefaultCell = ({ ...props }: DefaultCellProps): React.JSX.Element =
</EditableCellContextProvider>
</div>
)
}, [isInEditMode, props.getValue(), row, row.getIsSelected(), isEditable])
}, [isInEditMode, props.getValue(), row, row.getIsSelected(), isEditable, props.active])

function getCssClasses (): string[] {
const classes: string[] = []

if (props.active === true) {
classes.push('default-cell--active')
}

if (props.modified === true) {
classes.push('default-cell--modified')
}
Expand Down
25 changes: 17 additions & 8 deletions assets/js/src/core/components/grid/grid-cell/grid-cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,37 @@

import { type Cell, type CellContext, flexRender } from '@tanstack/react-table'
import React from 'react'
import { type ExtendedCellContext } from '../grid'
import { type GridCellReference, type ExtendedCellContext } from '../grid'
import { type GridContextProviderProps, GridContextProvider } from '../grid-context'
import {
DynamicTypeRegistryProvider
} from '@Pimcore/modules/element/dynamic-types/registry/provider/dynamic-type-registry-provider'

export interface GridCellProps {
cell: Cell<any, unknown>
isActive?: boolean
isModified?: boolean
onFocusCell?: (cell: GridCellReference) => void
tableElement: GridContextProviderProps['table']
}

export const GridCell = ({ cell, isModified, tableElement }: GridCellProps): React.JSX.Element => {
export const GridCell = ({ cell, isModified, isActive, onFocusCell, tableElement }: GridCellProps): React.JSX.Element => {
return (
<GridContextProvider table={ tableElement }>
<div className='grid__cell-content'>
{flexRender(cell.column.columnDef.cell, getExtendedCellContext(cell.getContext()))}
</div>
</GridContextProvider>
<DynamicTypeRegistryProvider serviceIds={ ['DynamicTypes/GridCellRegistry'] }>
<GridContextProvider table={ tableElement }>
<div className='grid__cell-content'>
{flexRender(cell.column.columnDef.cell, getExtendedCellContext(cell.getContext()))}
</div>
</GridContextProvider>
</DynamicTypeRegistryProvider>
)

function getExtendedCellContext (context: CellContext<any, any>): ExtendedCellContext {
return {
...context,
modified: isModified
active: isActive,
modified: isModified,
onFocus: onFocusCell
}
}
}
7 changes: 6 additions & 1 deletion assets/js/src/core/components/grid/grid-cell/grid-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ import React, { useMemo } from 'react'
import { GridCell } from './grid-cell'
import { type GridContextProviderProps } from '../grid-context'
import { type GridProps } from '@Pimcore/types/components/types'
import { type GridCellReference } from '@Pimcore/components/grid/grid'

export interface GridRowProps {
row: Row<any>
modifiedCells: string
isSelected?: boolean
tableElement: GridContextProviderProps['table']
columns: GridProps['columns']
activeColumId?: string
onFocusCell?: (cell: GridCellReference) => void
}

const GridRow = ({ row, isSelected, modifiedCells, ...props }: GridRowProps): React.JSX.Element => {
Expand Down Expand Up @@ -51,15 +54,17 @@ const GridRow = ({ row, isSelected, modifiedCells, ...props }: GridRowProps): Re
>
<GridCell
cell={ cell }
isActive={ props.activeColumId === cell.column.id }
isModified={ isModifiedCell(cell.column.id) }
key={ cell.id }
onFocusCell={ props.onFocusCell }
tableElement={ props.tableElement }
/>
</td>
))}
</tr>
)
}, [JSON.stringify(row), memoModifiedCells, isSelected, props.columns])
}, [JSON.stringify(row), memoModifiedCells, isSelected, props.columns, props.activeColumId])

function isModifiedCell (cellId: string): boolean {
return memoModifiedCells.find((item) => item.columnId === cellId) !== undefined
Expand Down
194 changes: 107 additions & 87 deletions assets/js/src/core/components/grid/grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
type TableOptions,
useReactTable
} from '@tanstack/react-table'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { isEmpty } from 'lodash'
import { useStyles } from './grid.styles'
import { Resizer } from './resizer/resizer'
Expand All @@ -36,7 +36,6 @@ import { useTranslation } from 'react-i18next'
import { Checkbox, Skeleton } from 'antd'
import { GridRow } from './grid-cell/grid-row'
import { SortButton, type SortDirection, SortDirections } from '../sort-button/sort-button'
import { DynamicTypeRegistryProvider } from '@Pimcore/modules/element/dynamic-types/registry/provider/dynamic-type-registry-provider'
import { type GridProps } from '@Pimcore/types/components/types'
import trackError, { GeneralError } from '@Pimcore/modules/app/error-handler'

Expand All @@ -55,22 +54,35 @@ declare module '@tanstack/react-table' {
}
}

export interface GridCellReference {
rowIndex: number
columnIndex: number
columnId: string
}

export interface ExtendedCellContext extends CellContext<any, any> {
modified?: boolean
active?: boolean
onFocus?: (cell: GridCellReference) => void
}

export const Grid = ({ enableMultipleRowSelection = false, modifiedCells = [], sorting, manualSorting = false, enableSorting = false, hideColumnHeaders = false, enableRowSelection = false, selectedRows = {}, ...props }: GridProps): React.JSX.Element => {
export const Grid = ({ enableMultipleRowSelection = false, modifiedCells = [], sorting, manualSorting = false, enableSorting = false, hideColumnHeaders = false, highlightActiveCell = false, onActiveCellChange, enableRowSelection = false, selectedRows = {}, ...props }: GridProps): React.JSX.Element => {
const { t } = useTranslation()
const hashId = useCssComponentHash('table')
const { styles } = useStyles()
const [columnResizeMode] = useState<ColumnResizeMode>('onEnd')
const [activeCell, setActiveCell] = useState<GridCellReference | undefined>()
const [tableAutoWidth, setTableAutoWidth] = useState<boolean>(props.autoWidth ?? false)
const tableElement = useRef<HTMLTableElement>(null)
const isRowSelectionEnabled = useMemo(() => enableMultipleRowSelection || enableRowSelection, [enableMultipleRowSelection, enableRowSelection])
const [internalSorting, setInternalSorting] = useState<SortingState>(sorting ?? [])
const memoModifiedCells = useMemo(() => { return modifiedCells ?? [] }, [JSON.stringify(modifiedCells)])
const autoColumnRef = useRef<HTMLTableCellElement>(null)

useEffect(() => {
onActiveCellChange?.(activeCell)
}, [activeCell])

useEffect(() => {
if (sorting !== undefined) {
setInternalSorting(sorting)
Expand Down Expand Up @@ -185,98 +197,106 @@ export const Grid = ({ enableMultipleRowSelection = false, modifiedCells = [], s

const table = useReactTable(tableProps)

const onFocusCell = useCallback((cell: GridCellReference) => {
setActiveCell(cell)
}, [])

return useMemo(() => (
<DynamicTypeRegistryProvider serviceIds={ ['DynamicTypes/GridCellRegistry'] }>
<div className={ ['ant-table-wrapper', hashId, styles.grid].join(' ') }>
<div className="ant-table ant-table-small">
<div className='ant-table-container'>
<div className='ant-table-content'>
<table
ref={ tableElement }
style={ { width: tableAutoWidth ? '100%' : table.getCenterTotalSize(), minWidth: table.getCenterTotalSize() } }
>
{ !hideColumnHeaders && (
<thead className='ant-table-thead'>
{table.getHeaderGroups().map(headerGroup => (
<tr key={ headerGroup.id }>
{headerGroup.headers.map((header, index) => (
<th
className='ant-table-cell'
key={ header.id }
ref={ header.column.columnDef.meta?.autoWidth === true ? autoColumnRef : null }
style={
header.column.columnDef.meta?.autoWidth === true && !header.column.getIsResizing()
? {
width: 'auto',
minWidth: header.column.getSize()
}
: {
width: header.column.getSize(),
maxWidth: header.column.getSize()
}
}
>
<div className='grid__cell-content'>
<span>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</span>

{header.column.getCanSort() && (
<div className='grid__sorter'>
<SortButton
allowUnsorted={ sorting === undefined }
onSortingChange={ (value) => { updateSortDirection(header.column, value) } }
value={ getSortDirection(header.column) }
/>
</div>
<div
className={ ['ant-table-wrapper', hashId, styles.grid].join(' ') }
>
<div className="ant-table ant-table-small">
<div className='ant-table-container'>
<div className='ant-table-content'>
<table
ref={ tableElement }
style={ { width: tableAutoWidth ? '100%' : table.getCenterTotalSize(), minWidth: table.getCenterTotalSize() } }
>
{ !hideColumnHeaders && (
<thead className='ant-table-thead'>
{table.getHeaderGroups().map(headerGroup => (
<tr key={ headerGroup.id }>
{headerGroup.headers.map((header, index) => (
<th
className='ant-table-cell'
key={ header.id }
ref={ header.column.columnDef.meta?.autoWidth === true ? autoColumnRef : null }
style={
header.column.columnDef.meta?.autoWidth === true && !header.column.getIsResizing()
? {
width: 'auto',
minWidth: header.column.getSize()
}
: {
width: header.column.getSize(),
maxWidth: header.column.getSize()
}
}
>
<div className='grid__cell-content'>
<span>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</span>

{header.column.getCanSort() && (
<div className='grid__sorter'>
<SortButton
allowUnsorted={ sorting === undefined }
onSortingChange={ (value) => { updateSortDirection(header.column, value) } }
value={ getSortDirection(header.column) }
/>
</div>

{props.resizable === true && header.column.getCanResize() && (
<Resizer
header={ header }
isResizing={ header.column.getIsResizing() }
table={ table }
/>
)}
</th>
))}
</tr>
))}
</thead>
)}
<tbody className="ant-table-tbody">
{table.getRowModel().rows.length === 0 && (
<tr className={ 'ant-table-row' }>
<td
className='ant-table-cell ant-table-cell__no-data'
colSpan={ table.getAllColumns().length }
>
{t('no-data-available-yet')}
</td>
</tr>
)}
{table.getRowModel().rows.map(row => (
<GridRow
columns={ columns }
isSelected={ row.getIsSelected() }
key={ row.id }
modifiedCells={ JSON.stringify(getModifiedRow(row.id)) }
row={ row }
tableElement={ tableElement }
/>
</div>

{props.resizable === true && header.column.getCanResize() && (
<Resizer
header={ header }
isResizing={ header.column.getIsResizing() }
table={ table }
/>
)}
</th>
))}
</tr>
))}
</tbody>
</table>
</div>
</thead>
)}
<tbody
className="ant-table-tbody"
>
{table.getRowModel().rows.length === 0 && (
<tr className={ 'ant-table-row' }>
<td
className='ant-table-cell ant-table-cell__no-data'
colSpan={ table.getAllColumns().length }
>
{t('no-data-available-yet')}
</td>
</tr>
)}
{table.getRowModel().rows.map(row => (
<GridRow
activeColumId={ highlightActiveCell === true && row.index === activeCell?.rowIndex ? activeCell.columnId : undefined }
columns={ columns }
isSelected={ row.getIsSelected() }
key={ row.id }
modifiedCells={ JSON.stringify(getModifiedRow(row.id)) }
onFocusCell={ onFocusCell }
row={ row }
tableElement={ tableElement }
/>
))}
</tbody>
</table>
</div>
</div>
</div>
</DynamicTypeRegistryProvider>
), [table, modifiedCells, data, columns, rowSelection, internalSorting])
</div>
), [table, modifiedCells, data, columns, rowSelection, internalSorting, (highlightActiveCell === true ? activeCell : undefined)])

function getModifiedRow (rowIndex: string): GridProps['modifiedCells'] {
return memoModifiedCells.filter(({ rowIndex: rIndex }) => String(rIndex) === String(rowIndex)) ?? []
Expand Down
Loading

0 comments on commit 468812e

Please sign in to comment.