Skip to content

Commit

Permalink
feat(DataTable): add support for table row statuses
Browse files Browse the repository at this point in the history
- component to support standard EDS statuses per row
- update tests and snapshots (new story for comparison)
  • Loading branch information
booc0mtaco committed Oct 17, 2024
1 parent 4942679 commit 7249f24
Show file tree
Hide file tree
Showing 6 changed files with 1,097 additions and 6 deletions.
28 changes: 28 additions & 0 deletions src/components/DataTable/DataTable.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@
}
}

.data-table__status-cell {
text-align: center;

.data-table--size-md & {
padding: calc(var(--eds-size-2) / 16 * 1rem)
calc(var(--eds-size-3) / 16 * 1rem);
}
.data-table--size-sm & {
padding: calc(var(--eds-size-half) / 16 * 1rem)
calc(var(--eds-size-1) / 16 * 1rem);
}
}


.data-table__cell {
display: flex;
gap: calc(var(--eds-size-1) / 16 * 1rem);
Expand Down Expand Up @@ -289,3 +303,17 @@
background-color: var(--eds-theme-color-background-utility-interactive-low-emphasis);
}
}

.data-table .data-table__status-cell {
.data-table--status-critical {
color: var(--eds-theme-color-icon-utility-critical)
}

.data-table--status-favorable {
color: var(--eds-theme-color-icon-utility-favorable);
}

.data-table--status-warning {
color: var(--eds-theme-color-icon-utility-warning);
}
}
99 changes: 99 additions & 0 deletions src/components/DataTable/DataTable.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
// We import all of the utilities from tanstack here, and this can contain other custom utilities
import { Button, Menu, Checkbox, DataTableUtils } from '../..';

import type { Status } from '../../util/variant-types';
import { chromaticViewports } from '../../util/viewports';

export default {
Expand Down Expand Up @@ -59,6 +60,8 @@ type Person = {
age: number;
visits: number;
progress: number;
// This column is used for tables that are eligible for status
status?: Extract<Status, 'critical' | 'favorable' | 'warning'>;
};

// Specifying the example (static) data for the table to use with tanstack primitives
Expand All @@ -76,6 +79,7 @@ const defaultData: Person[] = [
age: 40,
visits: 40,
progress: 80,
status: 'warning',
},
{
firstName: 'Tanner',
Expand All @@ -90,6 +94,7 @@ const defaultData: Person[] = [
age: 45,
visits: 20,
progress: 10,
status: 'critical',
},
{
firstName: 'Tandy',
Expand All @@ -111,6 +116,7 @@ const defaultData: Person[] = [
age: 45,
visits: 20,
progress: 10,
status: 'favorable',
},
{
firstName: 'Tandy',
Expand Down Expand Up @@ -700,6 +706,99 @@ export const Grouping: StoryObj<Args> = {
},
};

/**
* You can specify detailed statuses for each row in a table, matching a few common options. Extend the data type to include `status` which maps to the internal type
*
* Use the Utility type StatusDataTable (TODO-AH)
*
* TODO-AH:
* - what props to apply to the table (making room for the icon in each row?)
* - indent table caption and subcaption to align to status column?
*/
export const StatusRows: StoryObj<Args> = {
args: {
caption: 'Test table',
subcaption: 'Additional Subcaption',
isStatusEligible: true,
tableStyle: 'border',
rowStyle: 'lined',
},
render: (args) => {
const columns = [
columnHelper.accessor('status', {
header: () => {
<DataTable.HeaderCell aria-label="Status" />;
},
cell: (info) => (
<DataTable.StatusCell status={info.getValue()}></DataTable.StatusCell>
),
// TODO-AH: figure out cell size to make 28x32
size: 32,
}),
columnHelper.accessor('firstName', {
header: () => (
<DataTable.HeaderCell sortDirection="ascending">
First Name
</DataTable.HeaderCell>
),
cell: (info) => (
<DataTable.DataCell>{info.getValue()}</DataTable.DataCell>
),
}),
columnHelper.accessor((row) => row.lastName, {
id: 'lastName',
header: () => <DataTable.HeaderCell>Last Name</DataTable.HeaderCell>,
cell: (info) => (
<DataTable.DataCell>{info.getValue()}</DataTable.DataCell>
),
}),
columnHelper.accessor('age', {
header: () => (
<DataTable.HeaderCell alignment="trailing">Age</DataTable.HeaderCell>
),
cell: (info) => (
<DataTable.DataCell alignment="trailing">
{info.renderValue()}
</DataTable.DataCell>
),
}),
columnHelper.accessor('visits', {
header: () => (
<DataTable.HeaderCell alignment="trailing">
Visits
</DataTable.HeaderCell>
),
cell: (info) => (
<DataTable.DataCell alignment="trailing">
{info.renderValue()}
</DataTable.DataCell>
),
}),
columnHelper.accessor('progress', {
header: () => (
<DataTable.HeaderCell alignment="trailing">
Profile Progress
</DataTable.HeaderCell>
),
cell: (info) => (
<DataTable.DataCell alignment="trailing">
{info.renderValue()}
</DataTable.DataCell>
),
}),
];

// eslint-disable-next-line react-hooks/rules-of-hooks
const table = DataTableUtils.useReactTable({
data: defaultData,
columns,
getCoreRowModel: DataTableUtils.getCoreRowModel(),
});

return <DataTable {...args} table={table} />;
},
};

// TODO: Story for sticky column pinning (https://tanstack.com/table/latest/docs/framework/react/examples/column-pinning-sticky)

export const DefaultWithCustomTable: StoryObj<Args> = {
Expand Down
40 changes: 35 additions & 5 deletions src/components/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { flexRender, type Table } from '@tanstack/react-table';
import clsx from 'clsx';
import React, { useEffect } from 'react';

import getIconNameFromStatus from '../../util/getIconNameFromStatus';
import type { EDSBase, Size, Status, Align } from '../../util/variant-types';

import Button, { type ButtonProps } from '../Button';
Expand Down Expand Up @@ -46,7 +47,7 @@ export type DataTableProps<T = unknown> = EDSBase & {
*/
caption?: string;
/**
* Controls whether the rows allow for a status color/icon treatment.
* Controls whether the table allows rows for a status color/icon treatment.
*/
isStatusEligible?: boolean;
/**
Expand Down Expand Up @@ -77,11 +78,10 @@ export type DataTableProps<T = unknown> = EDSBase & {
export type DataTableTableProps = EDSBase &
Pick<DataTableProps, 'size' | 'tableStyle' | 'tableClassName' | 'rowStyle'>;

// TODO: Implement as followup
export type DataTableRowProps = Pick<EDSBase, 'children' | 'className'> & {
isInteractive?: boolean;
isSelected?: boolean;
status?: Extract<Status, 'error' | 'favorable' | 'warning'>;
status?: Extract<Status, 'critical' | 'favorable' | 'warning'>;
};

export type DataTableHeaderCellProps = EDSBase & {
Expand Down Expand Up @@ -129,6 +129,10 @@ export type DataTableDataCellProps = DataTableHeaderCellProps & {
children: React.ReactNode;
};

export type DataTableStatusCellProps = {
status?: Extract<Status, 'critical' | 'favorable' | 'warning'>;
};

/**
* `import {DataTable} from "@chanzuckerberg/eds";`
*
Expand All @@ -142,14 +146,15 @@ export function DataTable<T>({
className,
caption,
isInteractive = false,
isStatusEligible,
onSearchChange,
rowStyle = 'striped',
size = 'md',
subcaption,
table,
tableClassName,
tableStyle = 'basic',
...other
...rest
}: DataTableProps<T>) {
const componentClassName = clsx(styles['data-table'], className);

Expand All @@ -159,7 +164,7 @@ export function DataTable<T>({
* header, search field, and actions, and preserve accessibility.
*/
return (
<div className={componentClassName} {...other}>
<div className={componentClassName} {...rest}>
{(caption || subcaption || onSearchChange || actions) && (
<div className={styles['data-table__caption-container']}>
{(caption || subcaption) && (
Expand Down Expand Up @@ -254,6 +259,8 @@ export function DataTable<T>({
))}
</>
) : (
/* TODO-AH: handle the existence of status in the row model here and apply a color to the row background. it should override striped */
/* a11y for reading out status on table rows */
<DataTableRow
isInteractive={isInteractive}
isSelected={row.getIsSelected()}
Expand Down Expand Up @@ -396,6 +403,28 @@ export const DataTableDataCell = ({
);
};

export const DataTableStatusCell = ({
status,
...rest
}: DataTableStatusCellProps) => {
const statusCellClassName = clsx(
styles['data-table__status-cell'],
status && styles[`data-table--status-${status}`],
);

return (
<div className={statusCellClassName} {...rest}>
{status && (
<Icon
name={getIconNameFromStatus(status)}
purpose="decorative"
size="1.125rem"
/>
)}
</div>
);
};

export const DataTableTable = ({
children,
tableClassName,
Expand Down Expand Up @@ -528,3 +557,4 @@ DataTable.Row = DataTableRow;
DataTable.GroupRow = DataTableGroupRow;
DataTable.HeaderCell = DataTableHeaderCell;
DataTable.DataCell = DataTableDataCell;
DataTable.StatusCell = DataTableStatusCell;
Loading

0 comments on commit 7249f24

Please sign in to comment.