Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## [Unreleased] - TBD

### Added

- `DiamondDSIntegrations.mrtLoadingState(loading, data)` returns MRT's `showSkeletons`/`showProgressBars` (skeletons on first load, progress bar on refetch).
- `DiamondDSIntegrations.mrtRowIntent(intent)` returns row `sx` that tints an MRT row with the intent's subtle container colour.
- `DiamondDSIntegrations.skipWhileSkeleton(callback)` wraps an MRT row/cell/checkbox props callback so it is skipped while skeleton rows show.

### Changed

- _Dialog_ now defaults to elevation 16 (`surface.strong`'s tone) instead of MUI's 24, and temporary _Drawer_ to elevation 4 instead of 16, so floating surfaces order Drawer < Menu < Dialog and dialog text keeps more contrast.
Expand Down
57 changes: 52 additions & 5 deletions dev/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ import {
MaterialReactTable,
useMaterialReactTable,
} from "material-react-table";
import type { MRT_ColumnDef } from "material-react-table";
import type {
MRT_ColumnDef,
MRT_Row,
MRT_TableInstance,
} from "material-react-table";

import {
ThemeProvider,
Expand Down Expand Up @@ -100,7 +104,33 @@ export const SimpleMuiTableExample = () => {

<TableBody>
{data.map((row) => (
<TableRow key={row.id} hover selected={isSelected(row.id)}>
<TableRow
key={row.id}
hover
selected={isSelected(row.id)}
sx={
row.status === "Paused"
? {
"& td": {
backgroundColor: (theme) =>
(theme.vars || theme).palette.warning.container,
},
"&.Mui-selected td": {
backgroundImage:
"linear-gradient(rgb(var(--ds-primary-channel) / 0.08), rgb(var(--ds-primary-channel) / 0.08))",
},
"&:hover td": {
backgroundImage:
"linear-gradient(var(--ds-overlay-hover), var(--ds-overlay-hover))",
},
"&.Mui-selected:hover td": {
backgroundImage:
"linear-gradient(rgb(var(--ds-primary-channel) / 0.12), rgb(var(--ds-primary-channel) / 0.12))",
},
}
: undefined
}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected(row.id)}
Expand Down Expand Up @@ -131,6 +161,7 @@ export const SimpleMuiTableExample = () => {
/* MATERIAL REACT TABLE - mid-level (per-table) overrides */

export const MidLevelMrtExample = () => {
const mrtOptions = DiamondDSIntegrations.mrtOptions();
const table = useMaterialReactTable({
columns: mrtColumns,
data,
Expand All @@ -144,7 +175,23 @@ export const MidLevelMrtExample = () => {
getRowId: (row) => row.id,
initialState: { rowSelection: { "EXP-1003": true } },
mrtTheme: DiamondDSIntegrations.mrtTheme,
...DiamondDSIntegrations.mrtOptions(),
...mrtOptions,
// Tinted rows use the subtle container role so they follow dark mode.
muiTableBodyRowProps: DiamondDSIntegrations.skipWhileSkeleton(
({
row,
}: {
row: MRT_Row<Experiment>;
table: MRT_TableInstance<Experiment>;
}) => ({
...mrtOptions.muiTableBodyRowProps,
sx: {
...mrtOptions.muiTableBodyRowProps.sx,
...(row.original.status === "Paused" &&
DiamondDSIntegrations.mrtRowIntent("warning")),
},
}),
),
});

return <MaterialReactTable table={table} />;
Expand Down Expand Up @@ -196,7 +243,7 @@ const data: Experiment[] = [
id: "EXP-1002",
scientist: "Alan Turing",
beamline: "I03",
status: "Queued",
status: "Paused",
energy: 9.8,
},
{
Expand All @@ -217,7 +264,7 @@ const data: Experiment[] = [
id: "EXP-1005",
scientist: "Katherine Johnson",
beamline: "DIAD",
status: "Paused",
status: "Queued",
energy: 15.7,
},
];
Expand Down
41 changes: 41 additions & 0 deletions src/components/MUI/DataDisplay/Table-1.TableGuidance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,47 @@ import * as TableStories from "./Table.stories";

<Canvas of={TableStories.HoverSelectedRows} />

## Row emphasis

<p>
To tint a row by intent (for example a warning), use the intent's subtle{" "}
<strong>container</strong> colour, never the solid one, so the row stays
readable and follows dark mode. Do not hardcode amber or red values.
Use it sparingly, for warning or error rows only, and always pair it with a
status chip or icon: a tint must never be the only signal.
</p>

<pre className="ds-code-block">
{`<TableRow
sx={{
"& td, & th": {
backgroundColor: (theme) =>
(theme.vars || theme).palette.warning.container,
},
// Cell backgrounds hide the row's hover and selected states: layer them back.
"&.Mui-selected td, &.Mui-selected th": {
backgroundImage:
"linear-gradient(rgb(var(--ds-primary-channel) / 0.08), rgb(var(--ds-primary-channel) / 0.08))",
},
"&:hover td, &:hover th": {
backgroundImage:
"linear-gradient(var(--ds-overlay-hover), var(--ds-overlay-hover))",
},
"&.Mui-selected:hover td, &.Mui-selected:hover th": {
backgroundImage:
"linear-gradient(rgb(var(--ds-primary-channel) / 0.12), rgb(var(--ds-primary-channel) / 0.12))",
},
}}
/>`}
</pre>

<Canvas of={TableStories.WarningRow} />

<p>
In Material React Table, use <strong>mrtRowIntent()</strong> instead; see the
Material React Table guidance.
</p>

## Selection guidance

<p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,62 @@ enableHiding: true`}
});`}
</pre>

## Loading

<p>
Show one loading indicator at a time. Skeleton rows on first load, when there
is no data yet. A progress bar when refetching, so existing rows stay visible.
Do not show both, and do not use MRT's spinner overlay.
</p>

<pre className="ds-code-block">
{`const table = useMaterialReactTable({
columns,
data,
// Use these instead of isLoading, which adds a spinner overlay on top of the skeletons.
state: DiamondDSIntegrations.mrtLoadingState(loading, data),
});`}
</pre>

<p>
Row and checkbox style callbacks run on skeleton placeholder data. Wrap them
in <strong>DiamondDSIntegrations.skipWhileSkeleton()</strong> so they are
skipped while skeletons show.
</p>

## Row emphasis

<p>
To tint a row by intent (for example a warning), use{" "}
<strong>DiamondDSIntegrations.mrtRowIntent()</strong>. It applies the intent's
subtle container colour, never the solid one, so rows stay readable and follow
dark mode. Use it sparingly, for warning or error rows only, and always pair
it with a status chip or icon: a tint must never be the only signal. Spread it
after <strong>mrtOptions()</strong> row props, so it
replaces MRT's own hover and selected overlays. Wrap the callback in{" "}
<strong>skipWhileSkeleton()</strong> so skeleton rows are not tinted.
</p>

<pre className="ds-code-block">
{`const mrtOptions = DiamondDSIntegrations.mrtOptions();

const table = useMaterialReactTable({
columns,
data,
mrtTheme: DiamondDSIntegrations.mrtTheme,
...mrtOptions,
// Spread mrtOptions' row props so hover and selected styles are kept.
muiTableBodyRowProps: DiamondDSIntegrations.skipWhileSkeleton(({ row }) => ({
...mrtOptions.muiTableBodyRowProps,
sx: {
...mrtOptions.muiTableBodyRowProps.sx,
...(row.original.status === "Paused" &&
DiamondDSIntegrations.mrtRowIntent("warning")),
},
})),
});`}
</pre>

## Example configuration

<pre className="ds-code-block">
Expand Down
67 changes: 67 additions & 0 deletions src/components/MUI/DataDisplay/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,73 @@ export const HoverRows: Story = {
),
};

export const WarningRow: Story = {
parameters: {
docs: {
description: {
story:
"The warning tint reinforces the status chip. It never replaces it.",
},
},
},
render: (args) => (
<TableContainer>
<Table {...args} aria-label="tasks with a warning row">
<TableHead>
<TableRow>
<TableCell>Task</TableCell>
<TableCell>Step</TableCell>
<TableCell align="right">Status</TableCell>
</TableRow>
</TableHead>

<TableBody>
{tasks.map((task) => {
const paused = task.id === "TASK-3";

return (
<TableRow
hover
key={task.id}
sx={
paused
? {
"& td": {
backgroundColor: (theme) =>
(theme.vars || theme).palette.warning.container,
},
"&:hover td": {
backgroundImage:
"linear-gradient(var(--ds-overlay-hover), var(--ds-overlay-hover))",
},
}
: undefined
}
>
<TableCell>{task.id}</TableCell>
<TableCell>{task.name}</TableCell>

<TableCell align="right">
{paused ? (
<Chip
label="Paused"
color="warning"
size="small"
variant="outlined"
/>
) : (
renderStatusChip(task.status)
)}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
),
};

export const SelectedRows: Story = {
render: (args) => (
<TableContainer>
Expand Down
59 changes: 59 additions & 0 deletions src/themes/DiamondDSIntegrations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,62 @@ describe("DiamondDSIntegrations.mrtOptions", () => {
);
});
});

describe("DiamondDSIntegrations.mrtLoadingState", () => {
it("shows skeletons without data and a progress bar with data", () => {
const { mrtLoadingState } = DiamondDSIntegrations;

expect(mrtLoadingState(true, undefined)).toEqual({
showSkeletons: true,
showProgressBars: false,
});
expect(mrtLoadingState(true, [])).toEqual({
showSkeletons: true,
showProgressBars: false,
});
expect(mrtLoadingState(true, [1])).toEqual({
showSkeletons: false,
showProgressBars: true,
});
expect(mrtLoadingState(false, [])).toEqual({
showSkeletons: false,
showProgressBars: false,
});
});
});

describe("DiamondDSIntegrations.skipWhileSkeleton", () => {
it("returns no props while skeletons show", () => {
const wrapped = DiamondDSIntegrations.skipWhileSkeleton(() => ({ a: 1 }));
const table = (showSkeletons: boolean) => ({
getState: () => ({ showSkeletons }),
});

expect(wrapped({ table: table(true) })).toEqual({});
expect(wrapped({ table: table(false) })).toEqual({ a: 1 });
expect(
wrapped({ table: { getState: () => ({ isLoading: true }) } }),
).toEqual({});
});
});

describe("DiamondDSIntegrations.mrtRowIntent", () => {
it("tints rows with the intent's container token, mapping error to danger", () => {
const { mrtRowIntent } = DiamondDSIntegrations;

expect(mrtRowIntent("warning")["& td"].backgroundColor).toBe(
"var(--ds-warning-container)",
);
expect(
mrtRowIntent("warning")["&.Mui-selected td"].backgroundImage,
).toContain("--ds-primary-channel");
const merged = {
...DiamondDSIntegrations.mrtOptions().muiTableBodyRowProps.sx,
...mrtRowIntent("warning"),
};
expect(merged["&:hover td:after"]).toEqual({ display: "none" });
expect(mrtRowIntent("error")["& td"].backgroundColor).toBe(
"var(--ds-danger-container)",
);
});
});
Loading
Loading