Skip to content

Commit

Permalink
WCMS-21488: Manage Columns Feature for Data Table Tab (#234)
Browse files Browse the repository at this point in the history
  • Loading branch information
brdunfield authored Aug 9, 2024
1 parent 2be6751 commit 5bef7dc
Show file tree
Hide file tree
Showing 13 changed files with 664 additions and 16 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
"author": "",
"license": "GPL-3.0",
"dependencies": {
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@popperjs/core": "^2.11.6",
"@tanstack/react-query": "^5.14.1",
"@tanstack/react-table": "^8.7.9",
Expand Down
6 changes: 5 additions & 1 deletion src/components/DatasetTableTab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type DatasetTableTabProps = {
customColumns: Array<ColumnType>,
jsonUrl?: string,
dataDictionaryBanner: boolean,
manageColumnsEnabled: boolean,
}

const DatasetTable = ({
Expand All @@ -33,7 +34,8 @@ const DatasetTable = ({
rootUrl,
customColumns = [],
jsonUrl = undefined,
dataDictionaryBanner
dataDictionaryBanner,
manageColumnsEnabled,
}
: DatasetTableTabProps
) => {
Expand Down Expand Up @@ -83,13 +85,15 @@ const DatasetTable = ({
/> }
<div className="ds-u-border-x--1 ds-u-border-bottom--1">
<DataTable
id={id}
data={resource.values}
canResize={true}
columns={columns}
setSort={resource.setSort}
sortTransform={transformTableSortToQuerySort}
tablePadding={'ds-u-padding-y--2'}
loading={resource.loading}
manageColumnsEnabled={manageColumnsEnabled}
/>
</div>
{!resource.loading && (
Expand Down
40 changes: 37 additions & 3 deletions src/components/Datatable/Datatable.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useMemo } from "react";
import {
useReactTable,
flexRender,
Expand All @@ -10,15 +10,18 @@ import { Spinner, Alert } from "@cmsgov/design-system";
import TruncatedResizeableTHead from "./TruncatedResizeableTHead";
import FixedSizeTHead from "./FixedSizeTHead";
import "./datatable.scss";
import ManageColumns from "../ManageColumns/ManageColumns";

const DataTable = ({
id,
data,
columns,
setSort,
sortTransform,
tablePadding,
canResize,
loading = false,
manageColumnsEnabled,
}) => {
const [ sorting, setSorting ] = React.useState([])
const [ariaLiveFeedback, setAriaLiveFeedback] = useState('')
Expand All @@ -38,6 +41,19 @@ const DataTable = ({
})
)
})
const localStorageData = JSON.parse(localStorage.getItem(id));
const [columnOrder, setColumnOrder] = useState(() => {
if (manageColumnsEnabled && localStorageData)
return localStorageData.tableColumnOrder;
else
return table_columns.map(c => c.accessorKey);
})
const [columnVisibility, setColumnVisibility] = useState(() => {
if (manageColumnsEnabled && localStorageData)
return localStorageData.tableColumnVisibility;
else
return {};
})

const sortElement = (isSorted, onClickFn) => {
if(isSorted === 'asc') {
Expand All @@ -54,12 +70,14 @@ const DataTable = ({
columns: table_columns,
manualSorting: true,
state: {

columnOrder,
columnVisibility,
sorting,
},
columnResizeMode: 'onChange',
onSortingChange: setSorting,

onColumnOrderChange: setColumnOrder,
onColumnVisibilityChange: setColumnVisibility,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
debugTable: false,
Expand All @@ -70,8 +88,24 @@ const DataTable = ({
setSort(normalizedSort);
}, [sorting]);

const defaultColumnOrder = useMemo(() => table_columns.map(column => {
return column.accessorKey
}));

return(
<>
{ manageColumnsEnabled && (
<div>
<ManageColumns
id={id}
columns={table.getAllLeafColumns()}
columnOrder={columnOrder}
defaultColumnOrder={defaultColumnOrder}
setColumnOrder={setColumnOrder}
setColumnVisibility={setColumnVisibility}
/>
</div>
)}
<div className="dc-c-datatable-wrapper" tabIndex={0}>
<table
{...{
Expand Down
2 changes: 1 addition & 1 deletion src/components/Datatable/HeaderResizeElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const HeaderResizeElement = ({table, header, sortElement, setAriaLiveFeedback} :
className="ds-u-border-y--2 ds-u-padding--2 ds-u-border--dark ds-u-font-weight--bold"
>
<div className="ds-u-display--flex">
<span style={{maxWidth: header.getSize() - 16}} title={header.column.columnDef.header}>
<span style={{maxWidth: header.getSize() - 16}} title={typeof(header.column.columnDef.header) === "string" ? header.column.columnDef.header : ''}>
{header.isPlaceholder
? null
: flexRender(
Expand Down
61 changes: 61 additions & 0 deletions src/components/ManageColumns/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from "react";
import { CSSProperties } from "react";
import { Choice } from "@cmsgov/design-system";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";

const Card = ({id, visible, updateVisibility}: {id: string, visible: boolean, updateVisibility: Function}) => {
const {attributes, listeners, setNodeRef, transform, transition, isDragging} = useSortable({
id: id,
});

const style: CSSProperties = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.7 : 1,
zIndex: isDragging ? 1 : 0,
position: 'relative',
background: 'white',
touchAction: 'none'
};

return (
<li
className="ds-u-display--flex ds-u-justify-content--between ds-u-border-bottom--1"
ref={setNodeRef}
style={style}
{...listeners}
{...attributes}
onPointerUp={(e) => {
// Small hack to get around a chrome / webkit rendering bug = force chrome to repaint the checkbox
// For whatever reason the way dnd-kit handles events doesn't work well with chrome
// Without this code checkboxes can end up visually out of sync with app state until a repaint is forced
// this code forces the repaint without user interaction
const target = e.target as HTMLElement;
if (isDragging && target.tagName.toLowerCase() === "label") {
setTimeout(() => {
target.parentNode!.querySelector('input')!.checked = visible
}, 1)
}
}}
>
<Choice
type="checkbox"
label={id}
name={id + "_visibility"}
checked={visible}
className="ds-l-col--10 ds-u-margin-top--0 ds-u-margin-y--1 ds-u-padding-x--3"
labelClassName="dc-truncate"
value=""
onChange={() => {
updateVisibility(id, !visible)
}}
/>
<button className={`ds-l-col--2 dkan-manage-columns-reorder-button ${isDragging && 'grabbed'}`} aria-label={`Reorder ${id} column`}>
<span className="fa fa-sort"></span>
</button>
</li>
)
}

export default Card;
Loading

0 comments on commit 5bef7dc

Please sign in to comment.