From 1f581af858180bddefc08f8c042a912677ea5f2a Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Mon, 31 Oct 2022 17:04:18 +1100 Subject: [PATCH] use existing ColumnHeader to enable sort, column menu --- .../Table/ColumnHeader/ColumnHeader.tsx | 117 +++++++++--------- .../Table/ColumnHeader/ColumnHeaderSort.tsx | 39 +++--- src/components/Table/Styled/StyledResizer.tsx | 5 +- src/components/Table/Styled/StyledRow.tsx | 2 + src/components/Table/Styled/StyledTable.tsx | 2 + src/components/Table/Table.tsx | 51 ++++---- 6 files changed, 108 insertions(+), 108 deletions(-) diff --git a/src/components/Table/ColumnHeader/ColumnHeader.tsx b/src/components/Table/ColumnHeader/ColumnHeader.tsx index a1652c04..7ad33e72 100644 --- a/src/components/Table/ColumnHeader/ColumnHeader.tsx +++ b/src/components/Table/ColumnHeader/ColumnHeader.tsx @@ -1,36 +1,33 @@ -import { useRef } from "react"; +import { forwardRef, useRef } from "react"; import { useAtom, useSetAtom } from "jotai"; -import { useDrag, useDrop } from "react-dnd"; import { styled, - alpha, Tooltip, TooltipProps, tooltipClasses, Fade, Grid, + GridProps, IconButton, Typography, } from "@mui/material"; import DropdownIcon from "@mui/icons-material/MoreHoriz"; import LockIcon from "@mui/icons-material/LockOutlined"; -import ColumnHeaderSort from "./ColumnHeaderSort"; +import ColumnHeaderSort, { SORT_STATES } from "./ColumnHeaderSort"; -import { - projectScope, - userRolesAtom, - altPressAtom, -} from "@src/atoms/projectScope"; +import { projectScope, altPressAtom } from "@src/atoms/projectScope"; import { tableScope, - updateColumnAtom, columnMenuAtom, + tableSortsAtom, } from "@src/atoms/tableScope"; import { getFieldProp } from "@src/components/fields"; import { COLUMN_HEADER_HEIGHT } from "@src/components/Table/Column"; import { ColumnConfig } from "@src/types/table"; +import { FieldType } from "@src/constants/fields"; +import { spreadSx } from "@src/utils/ui"; export { COLUMN_HEADER_HEIGHT }; @@ -47,36 +44,20 @@ const LightTooltip = styled(({ className, ...props }: TooltipProps) => ( }, })); -export interface IDraggableHeaderRendererProps { +export interface IColumnHeaderProps extends Partial { column: ColumnConfig; + width: number; + focusInsideCell: boolean; + children: React.ReactNode; } -export default function DraggableHeaderRenderer({ - column, -}: IDraggableHeaderRendererProps) { - const [userRoles] = useAtom(userRolesAtom, projectScope); - const updateColumn = useSetAtom(updateColumnAtom, tableScope); +export const ColumnHeader = forwardRef(function ColumnHeader( + { column, width, focusInsideCell, children, ...props }: IColumnHeaderProps, + ref: React.Ref +) { const openColumnMenu = useSetAtom(columnMenuAtom, tableScope); const [altPress] = useAtom(altPressAtom, projectScope); - - const [{ isDragging }, dragRef] = useDrag({ - type: "COLUMN_DRAG", - item: { key: column.key }, - collect: (monitor) => ({ - isDragging: monitor.isDragging(), - }), - }); - - const [{ isOver }, dropRef] = useDrop({ - accept: "COLUMN_DRAG", - drop: ({ key }: { key: string }) => { - updateColumn({ key, config: {}, index: column.index }); - }, - collect: (monitor) => ({ - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - }), - }); + const [tableSorts] = useAtom(tableSortsAtom, tableScope); const buttonRef = useRef(null); @@ -85,14 +66,26 @@ export default function DraggableHeaderRenderer({ openColumnMenu({ column, anchorEl: buttonRef.current }); }; + const _sortKey = getFieldProp("sortKey", (column as any).type); + const sortKey = _sortKey ? `${column.key}.${_sortKey}` : column.key; + const currentSort: typeof SORT_STATES[number] = + tableSorts[0]?.key !== sortKey + ? "none" + : tableSorts[0]?.direction || "none"; + return ( { - dragRef(ref); - dropRef(ref); - }} + ref={ref} + {...props} + aria-sort={ + currentSort === "none" + ? "none" + : currentSort === "asc" + ? "ascending" + : "descending" + } container alignItems="center" wrap="nowrap" @@ -100,7 +93,8 @@ export default function DraggableHeaderRenderer({ sx={[ { height: "100%", - "& svg, & button": { display: "block" }, + "& svg, & button": { display: "block", zIndex: 1 }, + border: (theme) => `1px solid ${theme.palette.divider}`, color: "text.secondary", transition: (theme) => @@ -109,29 +103,18 @@ export default function DraggableHeaderRenderer({ }), "&:hover": { color: "text.primary" }, - cursor: "move", + position: "relative", py: 0, pr: 0.5, pl: 1, width: "100%", }, - isDragging - ? { opacity: 0.5 } - : isOver - ? { - backgroundColor: (theme) => - alpha( - theme.palette.primary.main, - theme.palette.action.focusOpacity - ), - color: "primary.main", - } - : {}, + ...spreadSx(props.sx), ]} className="column-header" > - {(column.width as number) > 140 && ( + {width > 140 && ( @@ -149,6 +132,7 @@ export default function DraggableHeaderRenderer({ onClick={() => { navigator.clipboard.writeText(column.key); }} + style={{ position: "relative", zIndex: 2 }} > {column.editable === false ? ( @@ -190,6 +174,8 @@ export default function DraggableHeaderRenderer({ fontWeight: "fontWeightMedium", lineHeight: `${COLUMN_HEADER_HEIGHT}px`, textOverflow: "clip", + position: "relative", + zIndex: 1, }} component="div" color="inherit" @@ -205,15 +191,22 @@ export default function DraggableHeaderRenderer({ - - - + {column.type !== FieldType.id && ( + + + + )} + + {children} ); -} +}); + +export default ColumnHeader; diff --git a/src/components/Table/ColumnHeader/ColumnHeaderSort.tsx b/src/components/Table/ColumnHeader/ColumnHeaderSort.tsx index deab488b..c8c4878f 100644 --- a/src/components/Table/ColumnHeader/ColumnHeaderSort.tsx +++ b/src/components/Table/ColumnHeader/ColumnHeaderSort.tsx @@ -1,4 +1,4 @@ -import { useAtom } from "jotai"; +import { useSetAtom } from "jotai"; import { colord } from "colord"; import { Tooltip, IconButton } from "@mui/material"; @@ -8,27 +8,22 @@ import IconSlash, { } from "@src/components/IconSlash"; import { tableScope, tableSortsAtom } from "@src/atoms/tableScope"; -import { FieldType } from "@src/constants/fields"; -import { getFieldProp } from "@src/components/fields"; -import { ColumnConfig } from "@src/types/table"; - -const SORT_STATES = ["none", "desc", "asc"] as const; +export const SORT_STATES = ["none", "desc", "asc"] as const; export interface IColumnHeaderSortProps { - column: ColumnConfig; + sortKey: string; + currentSort: typeof SORT_STATES[number]; + tabIndex?: number; } -export default function ColumnHeaderSort({ column }: IColumnHeaderSortProps) { - const [tableSorts, setTableSorts] = useAtom(tableSortsAtom, tableScope); +export default function ColumnHeaderSort({ + sortKey, + currentSort, + tabIndex, +}: IColumnHeaderSortProps) { + const setTableSorts = useSetAtom(tableSortsAtom, tableScope); - const _sortKey = getFieldProp("sortKey", (column as any).type); - const sortKey = _sortKey ? `${column.key}.${_sortKey}` : column.key; - - const currentSort: typeof SORT_STATES[number] = - tableSorts[0]?.key !== sortKey - ? "none" - : tableSorts[0]?.direction || "none"; const nextSort = SORT_STATES[SORT_STATES.indexOf(currentSort) + 1] ?? SORT_STATES[0]; @@ -37,8 +32,6 @@ export default function ColumnHeaderSort({ column }: IColumnHeaderSortProps) { else setTableSorts([{ key: sortKey, direction: nextSort }]); }; - if (column.type === FieldType.id) return null; - return ( colord(theme.palette.background.default) .mix( @@ -74,7 +68,8 @@ export default function ColumnHeaderSort({ column }: IColumnHeaderSortProps) { position: "relative", opacity: currentSort !== "none" ? 1 : 0, - ".column-header:hover &": { opacity: 1 }, + "[role='columnheader']:hover &, [role='columnheader']:focus &, [role='columnheader']:focus-within &, &:focus": + { opacity: 1 }, transition: (theme) => theme.transitions.create(["background-color", "opacity"], { @@ -89,7 +84,7 @@ export default function ColumnHeaderSort({ column }: IColumnHeaderSortProps) { transform: currentSort === "asc" ? "rotate(180deg)" : "none", }, - "&:hover .arrow": { + "&:hover .arrow, &:focus .arrow": { transform: currentSort === "asc" || nextSort === "asc" ? "rotate(180deg)" @@ -100,7 +95,7 @@ export default function ColumnHeaderSort({ column }: IColumnHeaderSortProps) { strokeDashoffset: currentSort === "none" ? 0 : ICON_SLASH_STROKE_DASHOFFSET, }, - "&:hover .icon-slash": { + "&:hover .icon-slash, &:focus .icon-slash": { strokeDashoffset: nextSort === "none" ? 0 : ICON_SLASH_STROKE_DASHOFFSET, }, diff --git a/src/components/Table/Styled/StyledResizer.tsx b/src/components/Table/Styled/StyledResizer.tsx index 83e005e0..7f23f1da 100644 --- a/src/components/Table/Styled/StyledResizer.tsx +++ b/src/components/Table/Styled/StyledResizer.tsx @@ -9,6 +9,7 @@ export const StyledResizer = styled("div", { shouldForwardProp: (prop) => prop !== "isResizing", })(({ theme, isResizing }) => ({ position: "absolute", + zIndex: 5, right: 0, top: 0, height: "100%", @@ -39,7 +40,7 @@ export const StyledResizer = styled("div", { height: "50%", width: 4, borderRadius: 2, - marginRight: 3, + marginRight: 2, background: isResizing ? theme.palette.primary.main @@ -51,3 +52,5 @@ export const StyledResizer = styled("div", { }, })); StyledResizer.displayName = "StyledResizer"; + +export default StyledResizer; diff --git a/src/components/Table/Styled/StyledRow.tsx b/src/components/Table/Styled/StyledRow.tsx index da2e11a6..5033152b 100644 --- a/src/components/Table/Styled/StyledRow.tsx +++ b/src/components/Table/Styled/StyledRow.tsx @@ -32,3 +32,5 @@ export const StyledRow = styled("div")(({ theme }) => ({ }, })); StyledRow.displayName = "StyledRow"; + +export default StyledRow; diff --git a/src/components/Table/Styled/StyledTable.tsx b/src/components/Table/Styled/StyledTable.tsx index 0033ee68..23c648e5 100644 --- a/src/components/Table/Styled/StyledTable.tsx +++ b/src/components/Table/Styled/StyledTable.tsx @@ -31,3 +31,5 @@ export const StyledTable = styled("div")(({ theme }) => ({ }, })); StyledTable.displayName = "StyledTable"; + +export default StyledTable; diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 4f2f51c3..fc9a2c8a 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -1,29 +1,27 @@ import { useMemo, useRef, useState, useEffect, useCallback } from "react"; import { useAtom, useSetAtom } from "jotai"; import { useThrottledCallback } from "use-debounce"; -import { - DragDropContext, - DropResult, - Droppable, - Draggable, -} from "react-beautiful-dnd"; - import { createColumnHelper, flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table"; +import { + DragDropContext, + DropResult, + Droppable, + Draggable, +} from "react-beautiful-dnd"; +import { Portal } from "@mui/material"; -import { StyledTable } from "./Styled/StyledTable"; -import { StyledRow } from "./Styled/StyledRow"; -import { StyledResizer } from "./Styled/StyledResizer"; -import ColumnHeaderComponent from "./Column"; +import StyledTable from "./Styled/StyledTable"; +import StyledRow from "./Styled/StyledRow"; +import ColumnHeader from "./ColumnHeader"; +import StyledResizer from "./Styled/StyledResizer"; import OutOfOrderIndicator from "./OutOfOrderIndicator"; +import ContextMenu from "./ContextMenu"; -import { IconButton, Portal } from "@mui/material"; - -import ColumnHeader, { COLUMN_HEADER_HEIGHT } from "./ColumnHeader"; import FinalColumnHeader from "./FinalColumnHeader"; import FinalColumn from "./formatters/FinalColumn"; // import TableRow from "./TableRow"; @@ -31,8 +29,6 @@ import EmptyState from "@src/components/EmptyState"; // import BulkActions from "./BulkActions"; import AddRow from "@src/components/TableToolbar/AddRow"; import { AddRow as AddRowIcon } from "@src/assets/icons"; -import Loading from "@src/components/Loading"; -import ContextMenu from "./ContextMenu"; import { projectScope, @@ -291,6 +287,8 @@ export default function TableComponent() { ref={provided.innerRef} > {headerGroup.headers.map((header) => { + if (!header.column.columnDef.meta) return null; + const isSelectedCell = (!selectedCell && header.index === 0) || (selectedCell?.path === "_rowy_header" && @@ -305,7 +303,7 @@ export default function TableComponent() { disableInteractiveElementBlocking > {(provided, snapshot) => ( -
{header.column.getCanResize() && ( @@ -370,7 +371,7 @@ export default function TableComponent() { onTouchStart={header.getResizeHandler()} /> )} - + )} );