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 2450235
Show file tree
Hide file tree
Showing 6 changed files with 1,166 additions and 7 deletions.
42 changes: 42 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,31 @@
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);
}
}

.data-table .data-table__row {
&.data-table--status-critical {
background-color: var(--eds-theme-color-background-utility-critical-low-emphasis);
}

&.data-table--status-favorable {
background-color: var(--eds-theme-color-background-utility-favorable-low-emphasis);
}

&.data-table--status-warning {
background-color: var(--eds-theme-color-background-utility-warning-low-emphasis);
}
}
97 changes: 97 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,97 @@ 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:
* - should 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.StatusHeaderCell />;
},
cell: (info) => <DataTable.StatusCell status={info.getValue()} />,
// 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
92 changes: 86 additions & 6 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,11 @@ 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'> & {
'aria-label'?: string;
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 +130,11 @@ export type DataTableDataCellProps = DataTableHeaderCellProps & {
children: React.ReactNode;
};

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

/**
* `import {DataTable} from "@chanzuckerberg/eds";`
*
Expand All @@ -142,14 +148,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 +166,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 @@ -257,6 +264,9 @@ export function DataTable<T>({
<DataTableRow
isInteractive={isInteractive}
isSelected={row.getIsSelected()}
status={
isStatusEligible ? row.getValue('status') : undefined
}
>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
Expand Down Expand Up @@ -396,6 +406,59 @@ export const DataTableDataCell = ({
);
};

export const DataTableStatusHeaderCell = ({
'aria-label': ariaLabel,
status,
...rest
}: DataTableStatusCellProps) => {
const statusLabel =
status &&
{
favorable: 'favorable',
critical: 'critical',
warning: 'warning',
}[status];

return (
<div className={styles['data-table__status-header-cell']} {...rest}>
{statusLabel}
</div>
);
};

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

// Set the label to manual, otherwise lookup the default label given "status"
const statusLabel =
ariaLabel ||
(status &&
{
favorable: 'favorable',
critical: 'critical',
warning: 'warning',
}[status]);

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

export const DataTableTable = ({
children,
tableClassName,
Expand Down Expand Up @@ -464,20 +527,33 @@ export const DataTableHeader = ({
};

export const DataTableRow = ({
'aria-label': ariaLabel,
children,
className,
isInteractive,
isSelected,
status,
...rest
}: DataTableRowProps) => {
const componnentClassName = clsx(
className,
styles['data-table__row'],
isInteractive && styles['data-table__row--is-interactive'],
isSelected && styles['data-table__row--is-selected'],
status && styles[`data-table--status-${status}`],
);

const rowA11yDesc =
ariaLabel ||
(status &&
{
favorable: 'This table row has a favorable status',
critical: 'This table row has a critical status',
warning: 'This table row has a warning status',
}[status]);

return (
<tr className={componnentClassName} {...rest}>
<tr aria-label={rowA11yDesc} className={componnentClassName} {...rest}>
{children}
</tr>
);
Expand Down Expand Up @@ -528,3 +604,7 @@ DataTable.Row = DataTableRow;
DataTable.GroupRow = DataTableGroupRow;
DataTable.HeaderCell = DataTableHeaderCell;
DataTable.DataCell = DataTableDataCell;

// Special Cell Sub-types
DataTable.StatusCell = DataTableStatusCell;
DataTable.StatusHeaderCell = DataTableStatusHeaderCell;
Loading

0 comments on commit 2450235

Please sign in to comment.