Skip to content

Commit

Permalink
Update V2 tables (#6826)
Browse files Browse the repository at this point in the history
* use new dnd library

* upped react table

* implement alternative dnd handler

* implement on change

* enable selection and dnd

* basic and a11y test

* small test refactor

* basic checkbox testing

* dnd sorting + checkbox tests

* enable groups

* custom sorter for dnd

* dnd children

* handle all dnd cases

* comments

* enabled more cy component tests

* dnd sort of children cy test

* better state management

* fixed types

* sort by headers

* better types

* internal state tracking + better header sorting + reset selections

* color code rows

* refactor helpers

* dnd helpers refactor + avoid  nesting groups

* dnd dropzone helpter test

* better group handling

* add id to form element

* correctly telegraph drop target

* added dynamic styles

* remove rotation

* stylings

* more dnd styles + hover states

* embeded button adjustments

* refactor + testing

* preserve original state

* fixed test

* initial sorting

* refactor & sorting+dnd

* refactored state sharing + real dnd for test

* remove duplicate id in story

* fold group with effect when dragging

* Better handling of selections

* more test + use realClick

* external sorting controll

* allow disable editing groups with dnd

* refactor helper to reduce iterations

* cleanup

* simplify + adjust styles

* more style adjustments

* adjusted ambeded button paddings

* fixed col table

* adjust children style marker

* visual adjustments and tests

* improved dnd-hover and children indicators

* refactor + customization test

* type fix

* removed unneeded dep from effect

* migrate ix dashboard

* tableV2 for suggestion + simple refactor

* reset selections on data changing

* sorting and selecting suggestions

* update test

* conditionally disable row selection

* fixed type issues

* adjusted table styles

* fix child shadow

* better group selections

* update suggestions with new table

* update children as subRows

* styles + type fixes

* e2e adjustments

* update translations

* fix button style
  • Loading branch information
Zasa-san authored Jul 26, 2024
1 parent d448791 commit cc2b44e
Show file tree
Hide file tree
Showing 56 changed files with 2,707 additions and 174 deletions.
76 changes: 62 additions & 14 deletions app/react/App/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -1688,10 +1688,6 @@ input[type="range"]::-ms-fill-lower {
z-index: 10;
}

.z-20 {
z-index: 20;
}

.z-40 {
z-index: 40;
}
Expand Down Expand Up @@ -2731,6 +2727,11 @@ input[type="range"]::-ms-fill-lower {
border-color: rgb(188 240 218 / var(--tw-border-opacity));
}

.border-indigo-100 {
--tw-border-opacity: 1;
border-color: rgb(229 237 255 / var(--tw-border-opacity));
}

.border-indigo-300 {
--tw-border-opacity: 1;
border-color: rgb(180 198 252 / var(--tw-border-opacity));
Expand Down Expand Up @@ -2874,11 +2875,21 @@ input[type="range"]::-ms-fill-lower {
background-color: rgb(49 196 141 / var(--tw-bg-opacity));
}

.bg-indigo-100 {
--tw-bg-opacity: 1;
background-color: rgb(229 237 255 / var(--tw-bg-opacity));
}

.bg-indigo-200 {
--tw-bg-opacity: 1;
background-color: rgb(205 219 254 / var(--tw-bg-opacity));
}

.bg-indigo-300 {
--tw-bg-opacity: 1;
background-color: rgb(180 198 252 / var(--tw-bg-opacity));
}

.bg-indigo-50 {
--tw-bg-opacity: 1;
background-color: rgb(240 245 255 / var(--tw-bg-opacity));
Expand Down Expand Up @@ -3075,11 +3086,6 @@ input[type="range"]::-ms-fill-lower {
padding-right: 2px;
}

.px-\[7px\] {
padding-left: 7px;
padding-right: 7px;
}

.py-0 {
padding-top: 0px;
padding-bottom: 0px;
Expand Down Expand Up @@ -3130,11 +3136,6 @@ input[type="range"]::-ms-fill-lower {
padding-bottom: 2px;
}

.py-\[3px\] {
padding-top: 3px;
padding-bottom: 3px;
}

.pb-14 {
padding-bottom: 3.5rem;
}
Expand Down Expand Up @@ -3509,6 +3510,11 @@ input[type="range"]::-ms-fill-lower {
color: rgb(49 46 129 / var(--tw-text-opacity));
}

.text-red-700 {
--tw-text-opacity: 1;
color: rgb(200 30 30 / var(--tw-text-opacity));
}

.text-red-800 {
--tw-text-opacity: 1;
color: rgb(155 28 28 / var(--tw-text-opacity));
Expand Down Expand Up @@ -3572,6 +3578,18 @@ input[type="range"]::-ms-fill-lower {
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.shadow-\[inset_0_-4px_\#3949AB\] {
--tw-shadow: inset 0 -4px #3949AB;
--tw-shadow-colored: inset 0 -4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.shadow-\[inset_5px_0px_0px_-1px_\#3949AB\] {
--tw-shadow: inset 5px 0px 0px -1px #3949AB;
--tw-shadow-colored: inset 5px 0px 0px -1px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.shadow-lg {
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
Expand Down Expand Up @@ -3631,6 +3649,12 @@ input[type="range"]::-ms-fill-lower {
transition-duration: 150ms;
}

.transition-colors {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}

.transition-opacity {
transition-property: opacity;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
Expand Down Expand Up @@ -3740,6 +3764,11 @@ input[type="range"]::-ms-fill-lower {
border-color: rgb(157 23 77 / var(--tw-border-opacity));
}

.hover\:border-indigo-200:hover {
--tw-border-opacity: 1;
border-color: rgb(205 219 254 / var(--tw-border-opacity));
}

.hover\:border-primary-300:hover {
--tw-border-opacity: 1;
border-color: rgb(165 180 252 / var(--tw-border-opacity));
Expand Down Expand Up @@ -3785,6 +3814,16 @@ input[type="range"]::-ms-fill-lower {
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
}

.hover\:bg-gray-50:hover {
--tw-bg-opacity: 1;
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
}

.hover\:bg-indigo-200:hover {
--tw-bg-opacity: 1;
background-color: rgb(205 219 254 / var(--tw-bg-opacity));
}

.hover\:bg-primary-800:hover {
--tw-bg-opacity: 1;
background-color: rgb(55 48 163 / var(--tw-bg-opacity));
Expand Down Expand Up @@ -4089,6 +4128,10 @@ input[type="range"]::-ms-fill-lower {
color: rgb(134 239 172 / var(--tw-text-opacity));
}

.disabled\:opacity-50:disabled {
opacity: 0.5;
}

.peer:checked ~ .peer-checked\:bg-primary-300 {
--tw-bg-opacity: 1;
background-color: rgb(165 180 252 / var(--tw-bg-opacity));
Expand Down Expand Up @@ -4156,6 +4199,11 @@ input[type="range"]::-ms-fill-lower {
color: rgb(156 163 175 / var(--tw-text-opacity));
}

:is(.dark .dark\:text-gray-500) {
--tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
}

:is(.dark .dark\:text-white) {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
Expand Down
12 changes: 4 additions & 8 deletions app/react/V2/Components/UI/EmbededButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const EmbededButton = ({
className = '',
}: ButtonProps) => {
let buttonColor = 'green';
let childrenBaseStyle = 'text-sm font-medium';
let childrenBaseStyle = 'text-sm';

switch (color) {
case 'orange':
Expand All @@ -40,7 +40,7 @@ const EmbededButton = ({
break;
case 'indigo':
buttonColor =
'text-indigo-700 bg-indigo-200 border-indigo-300 disabled:text-indigo-200 disabled:bg-indigo-50 disabled:border-indigo-200';
'text-indigo-800 bg-indigo-200 border-indigo-300 disabled:text-indigo-200 disabled:bg-indigo-50 disabled:border-indigo-200';
break;
case 'white':
buttonColor = 'bg-white border-gray-200 disabled:text-gray-300 disabled:bg-gray-50';
Expand All @@ -58,14 +58,10 @@ const EmbededButton = ({
type={type === 'submit' ? 'submit' : 'button'}
onClick={onClick}
disabled={disabled}
className={`${className} ${buttonColor}
${collapsed || disabled ? '' : 'border'}
${collapsed || disabled ? 'px-[7px] py-[3px]' : 'px-1.5 py-0.5'}
text-xs disabled:cursor-not-allowed font-medium rounded-[4px]
focus:outline-none`}
className={`${className} ${buttonColor} ${collapsed || disabled ? '' : 'border'} px-2 py-[2px] text-xs disabled:cursor-not-allowed font-medium rounded-[4px] focus:outline-none`}
form={form}
>
<div className="flex flex-row items-center justify-center gap-1">
<div className="flex flex-row gap-1 justify-center items-center">
<div className="w-3 h-3 text-sm">{icon}</div>
<div className={childrenBaseStyle}>{children}</div>
</div>
Expand Down
149 changes: 149 additions & 0 deletions app/react/V2/Components/UI/TableV2/DnDComponents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/* eslint-disable react/no-multi-comp */
/* eslint-disable react/jsx-props-no-spreading */
import React, { CSSProperties, useEffect, useState } from 'react';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { flexRender, Row } from '@tanstack/react-table';
import { Translate } from 'app/I18N';
import { TableRow } from './Table';

const dndHoverClass = 'shadow-[inset_0_-4px_#3949AB]';
const childIndicatorClass = 'shadow-[inset_5px_0px_0px_-1px_#3949AB]';

const inactiveGradientStyle: CSSProperties = {
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translate(-50%,-50%)',
width: '16px',
height: '80%',
background: 'radial-gradient(circle, #c5cae9 25%, transparent 26%) 0% 0% / 8px 10px',
};

const activeGradientStyle: CSSProperties = {
...inactiveGradientStyle,
background: 'radial-gradient(circle, #303f9f 25%, transparent 26%) 0% 0% / 8px 10px',
};

const getSytles = (expanded: boolean, isOver: boolean) => {
const expandedGroupStyles = expanded
? 'bg-indigo-100 border-indigo-100 hover:bg-indigo-200 hover:border-indigo-200'
: '';
const dndHoverStyles = isOver ? dndHoverClass : '';
return `${expandedGroupStyles} ${dndHoverStyles}`;
};

const RowDragHandleCell = <T extends TableRow<T>>({ row }: { row: Row<T> }) => {
const { attributes, listeners, isDragging } = useSortable({
id: row.id,
});
const [handlerStyle, setHandlerStyle] = useState(inactiveGradientStyle);

const canExpand = row.originalSubRows;
const expanded = row.getIsExpanded();

useEffect(() => {
if (canExpand && expanded && isDragging) {
row.toggleExpanded();
}
}, [isDragging]);

return (
<button
{...attributes}
{...listeners}
onMouseEnter={() => {
setHandlerStyle(activeGradientStyle);
}}
onMouseLeave={() => {
setHandlerStyle(inactiveGradientStyle);
}}
type="button"
style={isDragging ? activeGradientStyle : handlerStyle}
>
<span className="sr-only">
<Translate>Drag row</Translate>
</span>
</button>
);
};

const DraggableRow = <T extends TableRow<T>>({
row,
colSpan,
groupColumnIndex,
dndEnabled,
}: {
row: Row<T>;
colSpan: number;
groupColumnIndex: number;
dndEnabled: boolean;
}) => {
const expanded = row.getIsExpanded();
const isEmpty = row.originalSubRows?.length === 0;
const isChild = row.depth > 0;

const { transform, setNodeRef, isDragging, isOver } = useSortable({
id: row.id,
});

const { setNodeRef: dropNoderef, isOver: isOverDropzone } = useSortable({
id: `${row.id}-dropzone`,
});

const draggingStyles: CSSProperties = {
transformOrigin: 'left',
transform: CSS.Transform.toString({
x: 0,
y: transform?.y || 0,
scaleX: 0.7,
scaleY: 0.7,
}),
cursor: 'grabbing',
zIndex: 1,
position: 'relative',
opacity: 0.9,
outline: 'rgb(81 69 205) solid 4px',
backgroundColor: 'white',
};

const rowStyles = getSytles(expanded, isOver);

return (
<>
<tr
style={isDragging ? draggingStyles : undefined}
ref={setNodeRef}
className={`text-gray-900 border-b transition-colors hover:bg-gray-50 ${rowStyles}`}
>
{row.getVisibleCells().map((cell, index) => (
<td
key={cell.id}
className={`relative px-4 py-2 ${cell.column.columnDef.meta?.contentClassName} ${isChild && groupColumnIndex === index ? childIndicatorClass : ''}`}
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>

{isEmpty && expanded && (
<tr
ref={dropNoderef}
className={`border-b text-gray-900 transition-colors ${isOverDropzone ? dndHoverClass : ''}`}
>
<td className="px-4 py-3 text-sm italic text-gray-600" colSpan={colSpan}>
{dndEnabled ? (
<Translate>Empty group. Drop here to add</Translate>
) : (
<Translate>This group is empty</Translate>
)}
</td>
</tr>
)}
</>
);
};

const DnDHeader = () => <Translate className="sr-only">Empty</Translate>;

export { RowDragHandleCell, DraggableRow, DnDHeader };
28 changes: 28 additions & 0 deletions app/react/V2/Components/UI/TableV2/GroupComponents.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable react/no-multi-comp */
import React from 'react';
import { Row } from '@tanstack/react-table';
import { ChevronUpIcon, ChevronDownIcon } from '@heroicons/react/20/solid';
import { Translate } from 'app/I18N';
import { EmbededButton } from '../EmbededButton';
import { TableRow } from './Table';

const GroupHeader = () => <Translate className="sr-only">Empty</Translate>;

const GroupCell = <T extends TableRow<T>>({ row }: { row: Row<T> }) => {
const canExpand = row.originalSubRows;
const expanded = row.getIsExpanded();

return canExpand ? (
<EmbededButton
icon={expanded ? <ChevronUpIcon /> : <ChevronDownIcon />}
onClick={() => row.toggleExpanded()}
color="indigo"
className={`${expanded ? 'bg-indigo-300' : 'bg-indigo-100'} rounded-md border-none drop-shadow-none`}
>
<Translate className={`${expanded ? 'text-indigo-800' : 'text-indigo-700'}`}>Group</Translate>
<Translate className="sr-only">Open group</Translate>
</EmbededButton>
) : null;
};

export { GroupCell, GroupHeader };
Loading

0 comments on commit cc2b44e

Please sign in to comment.