From 7f9474adebd324d28f57fa18e07eb4b695ed93a7 Mon Sep 17 00:00:00 2001 From: Alessandro Cuppari Date: Wed, 27 Sep 2023 14:26:17 +0100 Subject: [PATCH] refactor: column utils to CSS modules --- .../src/body/.MRT_TableBodyCell.module.css | 17 ++ .../src/body/MRT_TableBodyCell.tsx | 89 +++++---- .../src/body/MRT_TableHeadCell.module.css | 14 ++ .../mantine-react-table/src/column.utils.ts | 173 +++++++----------- .../src/columns.utils.module.css | 47 +++++ .../src/footer/MRT_TableFooterCell.module.css | 13 +- .../src/footer/MRT_TableFooterCell.tsx | 40 ++-- .../src/head/MRT_TableHeadCell.tsx | 62 ++++--- 8 files changed, 260 insertions(+), 195 deletions(-) create mode 100644 packages/mantine-react-table/src/body/.MRT_TableBodyCell.module.css create mode 100644 packages/mantine-react-table/src/body/MRT_TableHeadCell.module.css create mode 100644 packages/mantine-react-table/src/columns.utils.module.css diff --git a/packages/mantine-react-table/src/body/.MRT_TableBodyCell.module.css b/packages/mantine-react-table/src/body/.MRT_TableBodyCell.module.css new file mode 100644 index 000000000..dfc0d8cbb --- /dev/null +++ b/packages/mantine-react-table/src/body/.MRT_TableBodyCell.module.css @@ -0,0 +1,17 @@ +.MRT_TableBodyCell { + align-items: var(--align-items); + cursor: var(--cursor); + justify-content: var(--align); + overflow: hidden; + padding-left: var(--padding-left); + white-space: var(--white-space); + z-index: var(--z-index); + :is([data-columndef="data"], [data-columndef="group"]) { + text-overflow: ellipsis; + } + @mixin hover { + outline: var(--outline); + outline-offset: -1px; + text-overflow: clip; + } +} diff --git a/packages/mantine-react-table/src/body/MRT_TableBodyCell.tsx b/packages/mantine-react-table/src/body/MRT_TableBodyCell.tsx index 41d8b5650..d1fad93ab 100644 --- a/packages/mantine-react-table/src/body/MRT_TableBodyCell.tsx +++ b/packages/mantine-react-table/src/body/MRT_TableBodyCell.tsx @@ -1,13 +1,14 @@ import { + type DragEvent, memo, + type MouseEvent, + type RefObject, useEffect, useMemo, useState, - type DragEvent, - type MouseEvent, - type RefObject, } from 'react'; import { Box, Skeleton, useMantineTheme } from '@mantine/core'; +import clsx from 'clsx'; import { MRT_EditCellTextInput } from '../inputs/MRT_EditCellTextInput'; import { MRT_CopyButton } from '../buttons/MRT_CopyButton'; import { MRT_TableBodyCellValue } from './MRT_TableBodyCellValue'; @@ -15,13 +16,13 @@ import { getCommonCellStyles, getIsFirstColumn, getIsLastColumn, - getPrimaryColor, } from '../column.utils'; import { type MRT_Cell, type MRT_TableInstance, type MRT_VirtualItem, } from '../types'; +import classes from './MRT_TableBodyRow.module.css'; interface Props = {}> { cell: MRT_Cell; @@ -122,9 +123,9 @@ export const MRT_TableBodyCell = = {}>({ const borderStyle = isDraggingColumn || isDraggingRow - ? `1px dashed ${theme.colors.gray[7]} !important` + ? '1px dashed var(--mantine-color-gray-7) !important' : isHoveredColumn || isHoveredRow - ? `2px dashed ${getPrimaryColor(theme)} !important` + ? '2px dashed var(--mantine-primary-color-filled) !important' : undefined; return borderStyle @@ -193,53 +194,65 @@ export const MRT_TableBodyCell = = {}>({ } }; + const { style, className, __vars } = getCommonCellStyles({ + column, + isStriped, + row, + table, + theme, + tableCellProps, + }); + return ( { if (node) { measureElement?.(node); } }} {...tableCellProps} - onDragEnter={handleDragEnter} - onDoubleClick={handleDoubleClick} - style={(theme) => ({ - alignItems: layoutMode === 'grid' ? 'center' : undefined, - cursor: + __vars={{ + ...__vars, + '--align-items': layoutMode === 'grid' ? 'center' : undefined, + '--cursor': isEditable && editDisplayMode === 'cell' ? 'pointer' : 'inherit', - justifyContent: - layoutMode === 'grid' ? tableCellProps.align : undefined, - overflow: 'hidden', - paddingLeft: + '--align': layoutMode === 'grid' ? tableCellProps.align : undefined, + '--padding-left': column.id === 'mrt-row-expand' ? `${row.depth + 1}rem !important` : undefined, - textOverflow: columnDefType !== 'display' ? 'ellipsis' : undefined, - whiteSpace: density === 'xs' ? 'nowrap' : 'normal', - zIndex: - draggingColumn?.id === column.id ? 2 : column.getIsPinned() ? 1 : 0, - '&:hover': { - outline: - isEditing && - ['table', 'cell'].includes(editDisplayMode ?? '') && - columnDefType !== 'display' - ? `1px solid ${theme.colors.gray[7]}` - : undefined, - outlineOffset: '-1px', - textOverflow: 'clip', - }, - ...getCommonCellStyles({ - column, - isStriped, - row, - table, - theme, - tableCellProps, - }), + '--white-space': density === 'xs' ? 'nowrap' : 'normal', + '--z-index': + draggingColumn?.id === column.id + ? 2 + : column.getIsPinned() + ? '1' + : '0', + '--outline': + isEditing && + ['table', 'cell'].includes(editDisplayMode ?? '') && + columnDefType !== 'display' + ? `1px solid var(--mantine-color-gray-7)` + : undefined, + ...tableCellProps.__vars, + }} + className={clsx( + className, + classes.MRT_TableBodyCell, + tableCellProps.className, + )} + onDragEnter={handleDragEnter} + onDoubleClick={handleDoubleClick} + style={{ + ...style, ...draggingBorders, - })} + }} > <> {cell.getIsPlaceholder() ? ( diff --git a/packages/mantine-react-table/src/body/MRT_TableHeadCell.module.css b/packages/mantine-react-table/src/body/MRT_TableHeadCell.module.css new file mode 100644 index 000000000..82c769302 --- /dev/null +++ b/packages/mantine-react-table/src/body/MRT_TableHeadCell.module.css @@ -0,0 +1,14 @@ +.MRT_TableHeadCell { + flex-direction: var(--flex-direction); + font-weight: bold; + overflow: visible; + padding: rem(var(--padding)); + user-select: var(--user-select); + vertical-align: top; + z-index: var(--z-index); + @mixin hover { + .mantine-ActionIcon-root { + opacity: 1; + } + } +} diff --git a/packages/mantine-react-table/src/column.utils.ts b/packages/mantine-react-table/src/column.utils.ts index 687e394d3..c9954024c 100644 --- a/packages/mantine-react-table/src/column.utils.ts +++ b/packages/mantine-react-table/src/column.utils.ts @@ -1,23 +1,20 @@ import { type ReactNode } from 'react'; import { - type Row, - type Renderable, - flexRender as _flexRender, createRow as _createRow, + flexRender as _flexRender, + type Renderable, + type Row, } from '@tanstack/react-table'; import { type MRT_AggregationFns } from './aggregationFns'; import { type MRT_FilterFns } from './filterFns'; import { type MRT_SortingFns } from './sortingFns'; import { - rgba, type BoxProps, + type CssVariable, + type CssVariables, type MantineTheme, - darken, - lighten, } from '@mantine/core'; import { - type MRT_TableOptions, - type MantineShade, type MRT_Column, type MRT_ColumnDef, type MRT_ColumnOrderState, @@ -28,7 +25,9 @@ import { type MRT_Header, type MRT_Row, type MRT_TableInstance, + type MRT_TableOptions, } from './types'; +import classes from './columns.utils.module.css'; export const getColumnId = = {}>( columnDef: MRT_ColumnDef, @@ -287,107 +286,83 @@ export const getCanRankRows = = {}>( !Object.values(expanded).some(Boolean) ); }; -// TODO: this needs to be refactored out to use CSS classes/styles + export const getCommonCellStyles = = {}>({ column, header, - isStriped, - row, table, tableCellProps, theme, }: { column: MRT_Column; header?: MRT_Header; - isStriped?: boolean; - row?: MRT_Row; table: MRT_TableInstance; tableCellProps: BoxProps; theme: MantineTheme; -}) => { +}): { __vars: CssVariables; className: string; style: CSSStyleDeclaration } => { + const __vars: Record = {}; + const headerId = `--${header ? 'header' : 'col'}-${parseCSSVarId( + header?.id ?? column.id, + )}-size`; const widthStyles = { - minWidth: `max(calc(var(--${header ? 'header' : 'col'}-${parseCSSVarId( - header?.id ?? column.id, - )}-size) * 1px), ${column.columnDef.minSize ?? 30}px)`, - width: `calc(var(--${header ? 'header' : 'col'}-${parseCSSVarId( - header?.id ?? column.id, - )}-size) * 1px)`, + minWidth: `max(calc(var(--header-id) * 1px), ${ + column.columnDef.minSize ?? 30 + }px)`, + width: `calc(var(--header-id) * 1px)`, }; + __vars['--header-id'] = headerId; + __vars['--col-size'] = `${column.columnDef.minSize ?? 30}px`; + __vars['--box-shadow'] = getIsLastLeftPinnedColumn(table, column) + ? '-4px 0 8px -6px rgba(var(--mantine-color-black, 0.2)) inset' + : getIsFirstRightPinnedColumn(column) + ? `4px 0 8px -6px rgba(var(--mantine-color-black, 0.2)) inset` + : 'transparent'; + __vars['--display'] = + table.options.layoutMode === 'grid' ? 'flex' : 'table-cell'; + __vars['--flex'] = + table.options.layoutMode === 'grid' ? 'var(--header-id) 0 auto' : ''; + __vars['--left'] = `${column.getStart('left')}px`; + __vars['--ml'] = + table.options.enableColumnVirtualization && + column.getIsPinned() === 'left' && + column.getPinnedIndex() === 0 + ? `-${ + column.getSize() * (table.getState().columnPinning.left?.length ?? 1) + }px` + : undefined; + __vars['--mr'] = + table.options.enableColumnVirtualization && + column.getIsPinned() === 'right' && + column.getPinnedIndex() === table.getVisibleLeafColumns().length - 1 + ? `-${ + column.getSize() * + (table.getState().columnPinning.right?.length ?? 1) * + 1.2 + }px` + : undefined; + __vars['--opacity'] = + table.getState().draggingColumn?.id === column.id || + table.getState().hoveredColumn?.id === column.id + ? '0.5' + : '1'; + __vars['--right'] = + column.getIsPinned() === 'right' + ? `${getTotalRight(table, column)}px` + : undefined; + __vars['--transition'] = table.options.enableColumnVirtualization + ? 'none' + : `padding 100ms ease-in-out`; return { - backgroundColor: row - ? row?.getIsSelected() - ? rgba(getPrimaryColor(theme), 0.1) - : column.getIsPinned() && column.columnDef.columnDefType !== 'group' - ? rgba( - theme.colorScheme === 'dark' - ? darken(theme.colors.dark[7], 0.02) - : theme.white, - 0.97, - ) - : isStriped - ? 'inherit' - : theme.colorScheme === 'dark' - ? lighten(theme.colors.dark[7], 0.02) - : theme.white - : 'inherit', - backgroundClip: 'padding-box', - boxShadow: getIsLastLeftPinnedColumn(table, column) - ? `-4px 0 8px -6px ${rgba(theme.black, 0.2)} inset` - : getIsFirstRightPinnedColumn(column) - ? `4px 0 8px -6px ${rgba(theme.black, 0.2)} inset` - : undefined, - display: table.options.layoutMode === 'grid' ? 'flex' : 'table-cell', - flex: - table.options.layoutMode === 'grid' - ? `var(--${header ? 'header' : 'col'}-${parseCSSVarId( - header?.id ?? column.id, - )}-size) 0 auto` - : undefined, - left: - column.getIsPinned() === 'left' - ? `${column.getStart('left')}px` - : undefined, - ml: - table.options.enableColumnVirtualization && - column.getIsPinned() === 'left' && - column.getPinnedIndex() === 0 - ? `-${ - column.getSize() * - (table.getState().columnPinning.left?.length ?? 1) - }px` - : undefined, - mr: - table.options.enableColumnVirtualization && - column.getIsPinned() === 'right' && - column.getPinnedIndex() === table.getVisibleLeafColumns().length - 1 - ? `-${ - column.getSize() * - (table.getState().columnPinning.right?.length ?? 1) * - 1.2 - }px` - : undefined, - opacity: - table.getState().draggingColumn?.id === column.id || - table.getState().hoveredColumn?.id === column.id - ? 0.5 - : 1, - position: - column.getIsPinned() && column.columnDef.columnDefType !== 'group' - ? 'sticky' - : undefined, - right: - column.getIsPinned() === 'right' - ? `${getTotalRight(table, column)}px` - : undefined, - transition: table.options.enableColumnVirtualization - ? 'none' - : `padding 100ms ease-in-out`, - ...(!table.options.enableColumnResizing && widthStyles), //let devs pass in width styles if column resizing is disabled - ...(tableCellProps?.style instanceof Function - ? tableCellProps.style(theme) - : (tableCellProps?.style as any)), - ...(table.options.enableColumnResizing && widthStyles), //do not let devs pass in width styles if column resizing is enabled + className: classes.MRT_ColumnCommonStyles, + style: { + ...(!table.options.enableColumnResizing && widthStyles), //let devs pass in width styles if column resizing is disabled + ...(tableCellProps?.style instanceof Function + ? tableCellProps.style(theme) + : (tableCellProps?.style as any)), + ...(table.options.enableColumnResizing && widthStyles), //do not let devs pass in width styles if column resizing is enabled + }, + __vars, }; }; @@ -413,18 +388,6 @@ export const MRT_DefaultDisplayColumn = { enableSorting: false, } as const; -export const getPrimaryShade = (theme: MantineTheme): number => - (theme.colorScheme === 'dark' - ? // @ts-ignore - theme.primaryShade?.dark ?? theme.primaryShade - : // @ts-ignore - theme.primaryShade?.light ?? theme.primaryShade) ?? 7; - -export const getPrimaryColor = ( - theme: MantineTheme, - shade?: MantineShade, -): string => theme.colors[theme.primaryColor][shade ?? getPrimaryShade(theme)]; - export const parseCSSVarId = (id: string) => id.replace(/[^a-zA-Z0-9]/g, '_'); export const flexRender = _flexRender as ( diff --git a/packages/mantine-react-table/src/columns.utils.module.css b/packages/mantine-react-table/src/columns.utils.module.css new file mode 100644 index 000000000..e0ee6d3f0 --- /dev/null +++ b/packages/mantine-react-table/src/columns.utils.module.css @@ -0,0 +1,47 @@ +.MRT_Column-common-styles { + --min-width: max(calc(var(--header-id) * 1px), var(--col-size)); + --width: calc(var(--header-id) * 1px); + --bg-color: color-mix(in srgb, var(--mantine-color-dark-7) .2%, var(--mantine-color-black)); + background-color: inherit; + // Not very sure about these attribute gymnastics... + [data-selected="true"] { + background-color: rgba(var(--mantine-primary-color-filled), 0.1); + } + :is([data-ispinned="left"], [data-ispinned="right"])[data-selected="false"][data-columndef="group"] { + @mixin light { + background-color: rgba(var(--mantine-color-white), 0.97); + } + @mixin dark { + --bg-color: color-mix(in srgb, var(--mantine-color-dark-7) .2%, var(--mantine-color-black)); + background-color: rgba(var(--bg-color) ,0.97); + } + } + [data-striped="true"][data-selected="false"][data-ispinned="false"] { + background-color: inherit; + } + [data-striped="true"][data-selected="false"][data-ispinned="false"] { + @mixin light { + background-color: var(--mantine-color-white); + } + @mixin dark { + background-color: rgba(var(--bg-color) ,0.97); + } + } + [data-ispinned="left"] { + left: var(--left); + } + :is([data-ispinned="left"], [data-ispinned="right"])[data-columndef="group"] { + position: sticky; + } + [data-ispinned="right"]{ + right: var(--right); + } + margin-left: var(--ml); + margin-right: var(--mr); + background-clip: padding-box; + display: var(--display); + box-shadow: var(--box-shadow); + flex: var(--flex); + opacity: var(--opacity); + transition: var(--transition); +} diff --git a/packages/mantine-react-table/src/footer/MRT_TableFooterCell.module.css b/packages/mantine-react-table/src/footer/MRT_TableFooterCell.module.css index 550a844db..2da09261c 100644 --- a/packages/mantine-react-table/src/footer/MRT_TableFooterCell.module.css +++ b/packages/mantine-react-table/src/footer/MRT_TableFooterCell.module.css @@ -1,18 +1,11 @@ .MRT_TableFooterCell { font-weight: bold; - // Not resolved? padding: rem(9px); vertical-align: top; + z-index: var(--z-index); + display: var(--display); - &--center-column { + [data-columndef="group"] { justify-content: center; } - - &--grid { - display: grid; - } - - &--table-cell { - display: table-cell; - } } diff --git a/packages/mantine-react-table/src/footer/MRT_TableFooterCell.tsx b/packages/mantine-react-table/src/footer/MRT_TableFooterCell.tsx index c3fba6118..3d28456de 100644 --- a/packages/mantine-react-table/src/footer/MRT_TableFooterCell.tsx +++ b/packages/mantine-react-table/src/footer/MRT_TableFooterCell.tsx @@ -35,29 +35,35 @@ export const MRT_TableFooterCell = = {}>({ ...mcTableFooterCellProps, }; + const { + className: commonClassName, + __vars, + style, + } = getCommonCellStyles({ + column, + table, + theme, + tableCellProps, + }); + return ( ({ - zIndex: column.getIsPinned() && columnDefType !== 'group' ? 2 : 1, - ...getCommonCellStyles({ - column, - table, - theme, - tableCellProps, - }), - })} + className={clsx(commonClassName, classes.MRT_TableFooterCell, className)} + __vars={{ + ...__vars, + '--z-index': + column.getIsPinned() && columnDefType !== 'group' ? '2' : '1', + '--display': layoutMode === 'grid' ? 'grid' : 'table-cell', + ...tableCellProps.__vars, + }} + style={style} > <> {footer.isPlaceholder diff --git a/packages/mantine-react-table/src/head/MRT_TableHeadCell.tsx b/packages/mantine-react-table/src/head/MRT_TableHeadCell.tsx index ba0d24a8d..00d4e2e5c 100644 --- a/packages/mantine-react-table/src/head/MRT_TableHeadCell.tsx +++ b/packages/mantine-react-table/src/head/MRT_TableHeadCell.tsx @@ -1,12 +1,13 @@ import { type DragEvent, type ReactNode, useMemo } from 'react'; import { Box, Flex, type MantineTheme, useMantineTheme } from '@mantine/core'; +import clsx from 'clsx'; import { MRT_ColumnActionMenu } from '../menus/MRT_ColumnActionMenu'; import { MRT_TableHeadCellFilterContainer } from './MRT_TableHeadCellFilterContainer'; import { MRT_TableHeadCellFilterLabel } from './MRT_TableHeadCellFilterLabel'; import { MRT_TableHeadCellGrabHandle } from './MRT_TableHeadCellGrabHandle'; import { MRT_TableHeadCellResizeHandle } from './MRT_TableHeadCellResizeHandle'; import { MRT_TableHeadCellSortLabel } from './MRT_TableHeadCellSortLabel'; -import { getCommonCellStyles, getPrimaryColor } from '../column.utils'; +import { getCommonCellStyles } from '../column.utils'; import { type MRT_Header, type MRT_TableInstance } from '../types'; import classes from './MRT_TableHeadCell.module.css'; @@ -80,9 +81,9 @@ export const MRT_TableHeadCell = = {}>({ const draggingBorder = useMemo( () => draggingColumn?.id === column.id - ? `1px dashed ${theme.colors.gray[7]} !important` + ? `1px dashed var(--mantine-color-gray-7) !important` : hoveredColumn?.id === column.id - ? `2px dashed ${getPrimaryColor(theme)} !important` + ? `2px dashed var(--mantine-color-gray-7) !important` : undefined, [draggingColumn, hoveredColumn], ); @@ -114,44 +115,55 @@ export const MRT_TableHeadCell = = {}>({ table, }) : columnDef?.Header ?? (columnDef.header as ReactNode); + const { className, __vars, style } = getCommonCellStyles({ + column, + header, + table, + tableCellProps, + theme, + }); return ( { if (node) { tableHeadCellRefs.current[column.id] = node; } }} {...tableCellProps} - style={(theme: MantineTheme) => ({ - flexDirection: layoutMode === 'grid' ? 'column' : undefined, - fontWeight: 'bold', - overflow: 'visible', - padding: density === 'xl' ? '23px' : density === 'md' ? '16px' : '10px', - userSelect: enableMultiSort && column.getCanSort() ? 'none' : undefined, - verticalAlign: 'top', - zIndex: + className={clsx( + className, + classes.MRT_TableHeadCell, + tableCellProps.className, + )} + __vars={{ + ...__vars, + '--flex-direction': layoutMode === 'grid' ? 'column' : undefined, + '--padding': + density === 'xl' ? '23px' : density === 'md' ? '16px' : '10px', + '--user-select': + enableMultiSort && column.getCanSort() ? 'none' : undefined, + '--z-index': column.getIsResizing() || draggingColumn?.id === column.id - ? 3 + ? '3' : column.getIsPinned() && columnDefType !== 'group' - ? 2 - : 1, - '&:hover .mantine-ActionIcon-root': { - opacity: 1, - }, - ...getCommonCellStyles({ - column, - header, - table, - tableCellProps, - theme, - }), + ? '2' + : '1', + ...tableCellProps.__vars, + }} + style={{ + ...style, ...draggingBorders, - })} + }} > {header.isPlaceholder ? null : (