Files
rowy/www/src/components/Table/ColumnHeader.tsx
2021-02-18 11:27:57 +08:00

294 lines
7.6 KiB
TypeScript

import React from "react";
import clsx from "clsx";
import { HeaderRendererProps } from "react-data-grid";
import { useDrag, useDrop, DragObjectWithType } from "react-dnd";
import { useCombinedRefs } from "react-data-grid/lib/hooks";
import {
makeStyles,
createStyles,
fade,
Tooltip,
Fade,
Grid,
IconButton,
Typography,
} from "@material-ui/core";
import SortDescIcon from "@material-ui/icons/ArrowDownward";
import DropdownIcon from "@material-ui/icons/ArrowDropDownCircle";
import { FieldType } from "constants/fields";
import { getFieldProp } from "components/fields";
import { useFiretableContext } from "contexts/FiretableContext";
import { FiretableOrderBy } from "hooks/useFiretable";
const useStyles = makeStyles((theme) =>
createStyles({
root: {
height: "100%",
"& svg, & button": { display: "block" },
color: theme.palette.text.secondary,
transition: theme.transitions.create("color", {
duration: theme.transitions.duration.short,
}),
"&:hover": { color: theme.palette.text.primary },
cursor: "move",
margin: theme.spacing(0, -1.5),
padding: theme.spacing(0, 1.5),
width: `calc(100% + ${theme.spacing(1.5) * 2}px)`,
},
isDragging: { opacity: 0.5 },
isOver: {
backgroundColor: fade(
theme.palette.primary.main,
theme.palette.action.focusOpacity
),
color: theme.palette.primary.main,
},
columnNameContainer: {
flexShrink: 1,
overflow: "hidden",
margin: theme.spacing(0, 0.5),
marginRight: -26,
},
columnName: { lineHeight: "44px" },
columnNameTooltip: {
background: theme.palette.background.default,
color: theme.palette.text.primary,
margin: "-43px 0 0",
padding: theme.spacing(0, 1.5, 0, 0),
"& *": { lineHeight: "42px" },
},
sortIconContainer: {
backgroundColor: theme.palette.background.default,
width: 30,
height: 30,
opacity: 0,
transition: theme.transitions.create("opacity", {
duration: theme.transitions.duration.shortest,
}),
"$root:hover &": { opacity: 1 },
},
sortIconContainerSorted: { opacity: 1 },
sortIcon: {
transition: theme.transitions.create(["background-color", "transform"], {
duration: theme.transitions.duration.short,
}),
},
sortIconAsc: {
transform: "rotate(180deg)",
},
dropdownButton: {
opacity: 0.5,
transition: theme.transitions.create("opacity", {
duration: theme.transitions.duration.short,
}),
"&:hover": {
backgroundColor: "transparent",
opacity: 1,
},
padding: 0,
},
})
);
interface ColumnDragObject extends DragObjectWithType {
key: string;
}
export default function DraggableHeaderRenderer<R>({
column,
}: HeaderRendererProps<R> & {
onColumnsReorder: (sourceKey: string, targetKey: string) => void;
}) {
const classes = useStyles();
const {
tableState,
tableActions,
userClaims,
columnMenuRef,
} = useFiretableContext();
const [{ isDragging }, drag] = useDrag({
item: { key: column.key, type: "COLUMN_DRAG" },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
});
const [{ isOver }, drop] = useDrop({
accept: "COLUMN_DRAG",
drop({ key, type }: ColumnDragObject) {
if (type === "COLUMN_DRAG") {
// onColumnsReorder(key, props.column.key);
tableActions?.column.reorder(key, column.key);
}
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
}),
});
const headerRef = useCombinedRefs(drag, drop);
if (!columnMenuRef || !tableState || !tableActions) return null;
const { orderBy } = tableState;
const handleClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) =>
columnMenuRef?.current?.setSelectedColumnHeader({
column,
anchorEl: event.currentTarget,
});
const isSorted = orderBy?.[0]?.key === (column.key as string);
const isAsc = isSorted && orderBy?.[0]?.direction === "asc";
const handleSortClick = () => {
if (isAsc) {
const ordering: FiretableOrderBy = [
{ key: column.key as string, direction: "desc" },
];
tableActions.table.orderBy(ordering);
} else {
const ordering: FiretableOrderBy = [
{ key: column.key as string, direction: "asc" },
];
tableActions.table.orderBy(ordering);
}
};
return (
<Grid
ref={headerRef}
container
className={clsx(
classes.root,
isDragging && classes.isDragging,
isOver && !isDragging && classes.isOver
)}
alignItems="center"
wrap="nowrap"
>
<Tooltip
title={
<>
<Typography variant="caption" component="p">
{column.key as string}
</Typography>
<Typography variant="body2" component="p">
<small>(Click to copy)</small>
</Typography>
</>
}
enterDelay={1000}
placement="bottom-start"
>
<Grid
item
onClick={() => {
navigator.clipboard.writeText(column.key as string);
}}
>
{getFieldProp("icon", (column as any).type)}
</Grid>
</Tooltip>
<Grid item xs className={classes.columnNameContainer}>
<Tooltip
title={<Typography variant="caption">{column.name}</Typography>}
enterDelay={1000}
placement="bottom-start"
PopperProps={{
modifiers: {
flip: { enabled: false },
preventOverflow: {
enabled: false,
boundariesElement: "scrollParent",
},
hide: { enabled: false },
},
}}
TransitionComponent={Fade}
classes={{ tooltip: classes.columnNameTooltip }}
>
<Typography
variant="caption"
noWrap
className={classes.columnName}
component="div"
color="inherit"
>
{column.name}
</Typography>
</Tooltip>
</Grid>
{(column as any).type !== FieldType.id && (
<Grid
item
className={clsx(
classes.sortIconContainer,
isSorted && classes.sortIconContainerSorted
)}
>
<IconButton
disableFocusRipple={true}
size="small"
onClick={handleSortClick}
color="inherit"
aria-label={`Sort by ${isAsc ? "descending" : "ascending"}`}
className={clsx(classes.sortIcon, isAsc && classes.sortIconAsc)}
>
<SortDescIcon />
</IconButton>
</Grid>
)}
{(userClaims?.roles?.includes("ADMIN") ||
(userClaims?.roles?.includes("OPS") &&
[FieldType.multiSelect, FieldType.singleSelect].includes(
(column as any).type
))) && (
<Grid item>
<IconButton
size="small"
className={classes.dropdownButton}
aria-label={`Show ${column.name} column dropdown`}
color="inherit"
onClick={handleClick}
>
<DropdownIcon />
</IconButton>
</Grid>
)}
</Grid>
);
// return (
// <div
// ref={useCombinedRefs(drag, drop)}
// style={{
// opacity: isDragging ? 0.5 : 1,
// backgroundColor: isOver ? '#ececec' : 'inherit',
// cursor: 'move'
// }}
// >
// {props.column.name}
// </div>
// );
}