From d4acd357c61f12d085a944c53c55a629c86183ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tha=C3=AFs=20Guigon?= Date: Fri, 28 Jul 2023 14:47:20 +0200 Subject: [PATCH] feat: add column resizing Closes #817 --- .../ui/table/components/ColumnHead.tsx | 9 +- .../ui/table/components/EntityTableHeader.tsx | 100 +++++++++++++++++- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/front/src/modules/ui/table/components/ColumnHead.tsx b/front/src/modules/ui/table/components/ColumnHead.tsx index 4a3b3d58559e..5f55b49f5719 100644 --- a/front/src/modules/ui/table/components/ColumnHead.tsx +++ b/front/src/modules/ui/table/components/ColumnHead.tsx @@ -13,6 +13,7 @@ const StyledTitle = styled.div` font-weight: ${({ theme }) => theme.font.weight.medium}; height: ${({ theme }) => theme.spacing(8)}; padding-left: ${({ theme }) => theme.spacing(2)}; + padding-right: ${({ theme }) => theme.spacing(2)}; `; const StyledIcon = styled.div` @@ -20,11 +21,17 @@ const StyledIcon = styled.div` margin-right: ${({ theme }) => theme.spacing(1)}; `; +const StyledText = styled.span` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + export function ColumnHead({ viewName, viewIcon }: OwnProps) { return ( {viewIcon} - {viewName} + {viewName} ); } diff --git a/front/src/modules/ui/table/components/EntityTableHeader.tsx b/front/src/modules/ui/table/components/EntityTableHeader.tsx index 292962fea1b4..a9025b47817b 100644 --- a/front/src/modules/ui/table/components/EntityTableHeader.tsx +++ b/front/src/modules/ui/table/components/EntityTableHeader.tsx @@ -1,13 +1,91 @@ +import { DragEvent, useCallback, useRef, useState } from 'react'; +import styled from '@emotion/styled'; + import { TableColumn } from '@/people/table/components/peopleColumns'; import { ColumnHead } from './ColumnHead'; import { SelectAllCheckbox } from './SelectAllCheckbox'; +const COLUMN_MIN_WIDTH = 75; + +const StyledColumnHeaderCell = styled.th<{ isResizing?: boolean }>` + min-width: ${COLUMN_MIN_WIDTH}px; + position: relative; + + ${(props) => + props.isResizing + ? `&:after { + background-color: ${props.theme.color.blue}; + bottom: 0; + content: ''; + display: block; + position: absolute; + right: -1.5px; + top: 0; + width: 2px; + }` + : ''} +`; + +const StyledResizeHandler = styled.div` + bottom: 0; + cursor: col-resize; + padding: 0 ${({ theme }) => theme.spacing(2)}; + position: absolute; + right: -9px; + top: 0; + width: 1px; + z-index: 1; +`; + export function EntityTableHeader({ columns, }: { columns: Array; }) { + const [columnWidths, setColumnWidths] = useState( + columns.reduce>( + (result, column) => ({ ...result, [column.id]: column.size }), + {}, + ), + ); + const [offset, setOffset] = useState(0); + const resizedColumnId = useRef(''); + const initialHandlerPosition = useRef(-1); + + const handleResizeHandlerDragStart = useCallback( + (event: DragEvent, columnId: string) => { + resizedColumnId.current = columnId; + initialHandlerPosition.current = event.clientX; + }, + [], + ); + + const handleResizeHandlerDrag = useCallback( + (event: DragEvent) => { + // @see https://stackoverflow.com/q/36308460 + // `event.screenX === 0` allows to detect the last "drag" event + // which is emitted on mouse release. + // On this last "drag" event, `event.clientX` is reset to an incorrect value + // which does not reflect the handler's position, so we ignore it. + if (event.screenX === 0) return; + + setOffset(event.clientX - initialHandlerPosition.current); + }, + [], + ); + + const handleResizeHandlerDragEnd = useCallback(() => { + setColumnWidths((previousColumnWidths) => ({ + ...previousColumnWidths, + [resizedColumnId.current]: Math.max( + previousColumnWidths[resizedColumnId.current] + offset, + COLUMN_MIN_WIDTH, + ), + })); + resizedColumnId.current = ''; + }, [offset]); + return ( @@ -21,16 +99,28 @@ export function EntityTableHeader({ {columns.map((column) => ( - - + + handleResizeHandlerDragStart(event, column.id) + } + onDrag={handleResizeHandlerDrag} + onDragEnd={handleResizeHandlerDragEnd} + /> + ))}