react-data-matrix 0.4.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +416 -280
- package/dist/components/MatrixHeaders.d.ts +8 -0
- package/dist/components/MatrixRows.d.ts +16 -0
- package/dist/components/TableData.d.ts +17 -0
- package/dist/grid.d.ts +13 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1414 -0
- package/dist/theme/baseCss.d.ts +2 -0
- package/dist/theme/colour.d.ts +10 -0
- package/dist/theme/presets/arcade.d.ts +2 -0
- package/dist/theme/presets/aurora.d.ts +2 -0
- package/dist/theme/presets/beacon.d.ts +2 -0
- package/dist/theme/presets/blueprint.d.ts +2 -0
- package/dist/theme/presets/boardroom.d.ts +2 -0
- package/dist/theme/presets/broadsheet.d.ts +2 -0
- package/dist/theme/presets/brutal.d.ts +2 -0
- package/dist/theme/presets/canopy.d.ts +2 -0
- package/dist/theme/presets/clay.d.ts +2 -0
- package/dist/theme/presets/contour.d.ts +2 -0
- package/dist/theme/presets/fjord.d.ts +2 -0
- package/dist/theme/presets/glasshouse.d.ts +2 -0
- package/dist/theme/presets/graphite.d.ts +2 -0
- package/dist/theme/presets/index.d.ts +57 -0
- package/dist/theme/presets/ledger.d.ts +2 -0
- package/dist/theme/presets/midnight.d.ts +2 -0
- package/dist/theme/presets/neon.d.ts +2 -0
- package/dist/theme/presets/noir.d.ts +2 -0
- package/dist/theme/presets/original.d.ts +2 -0
- package/dist/theme/presets/sherbet.d.ts +2 -0
- package/dist/theme/presets/signal.d.ts +2 -0
- package/dist/theme/presets/sunset.d.ts +2 -0
- package/dist/theme/presets/swiss.d.ts +2 -0
- package/dist/theme/presets/terminal.d.ts +2 -0
- package/dist/theme/presets/thermal.d.ts +2 -0
- package/dist/theme/presets/tidewater.d.ts +2 -0
- package/dist/theme/presets/whitespace.d.ts +2 -0
- package/dist/theme/severity.d.ts +18 -0
- package/dist/theme/tokens.d.ts +11 -0
- package/dist/theme/types.d.ts +36 -0
- package/dist/types/index.d.ts +71 -0
- package/package.json +50 -37
- package/dist/lib/components/MatrixHeaders.d.ts +0 -4
- package/dist/lib/components/MatrixRows.d.ts +0 -4
- package/dist/lib/components/TableData.d.ts +0 -4
- package/dist/lib/helpers/getStyles.d.ts +0 -12
- package/dist/lib/index.d.ts +0 -4
- package/dist/lib/types/index.d.ts +0 -102
- package/dist/lib/utils/data.d.ts +0 -2
- package/dist/lib/utils/functions.d.ts +0 -4
- package/dist/manifest.json +0 -7
- package/dist/react-data-matrix.es.js +0 -913
- package/dist/react-data-matrix.umd.js +0 -28
package/README.md
CHANGED
|
@@ -1,364 +1,500 @@
|
|
|
1
|
-
#
|
|
1
|
+
# react-data-matrix
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A React component for risk matrices and other grids where a value sits at the crossing of two axes — likelihood × consequence, impact × effort, probability × severity.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**[See all 26 designs in the live gallery →](https://bronz3beard.github.io/react-matrix/)**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- **26 ready-made designs**, usable as they are and easy to adjust.
|
|
8
|
+
- **Zero runtime dependencies.** React is yours; nothing else ships.
|
|
9
|
+
- **~5 kB gzipped** for the component and its default design; about 335 B per extra design, and unused designs are removed by your bundler.
|
|
10
|
+
- **Accessible by default:** a real table with captions and header scopes, keyboard-operable cells, keyboard-scrollable on small screens, and contrast-checked designs.
|
|
11
|
+
- **Works under a strict Content Security Policy**, verified in Chromium, WebKit and Firefox.
|
|
12
|
+
- **React 18.3 and React 19**, including server rendering.
|
|
8
13
|
|
|
9
|
-
#
|
|
14
|
+
> **Version note.** This README documents the 1.0 API. The published npm version is still 0.4.x; see [Migrating from 0.4.x](#migrating-from-04x).
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install react-data-matrix
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
React is a peer dependency:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{ "react": "^18.3.0 || ^19.0.0", "react-dom": "^18.3.0 || ^19.0.0" }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The package is ESM-only and ships TypeScript types.
|
|
12
31
|
|
|
13
|
-
|
|
32
|
+
## Quick start
|
|
14
33
|
|
|
15
34
|
```tsx
|
|
16
|
-
import ReactMatrix from
|
|
35
|
+
import ReactMatrix, { type MatrixData } from 'react-data-matrix';
|
|
17
36
|
|
|
18
|
-
const
|
|
19
|
-
|
|
37
|
+
const data: MatrixData = {
|
|
38
|
+
/* see Data shape below */
|
|
39
|
+
};
|
|
20
40
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
{...{
|
|
24
|
-
matrixName: "",
|
|
25
|
-
matrixDescription: "",
|
|
26
|
-
hasTableBorder: true,
|
|
27
|
-
rowPrimaryUpper: true,
|
|
28
|
-
hasInlineStyles: true,
|
|
29
|
-
matrixSizeSelected: 5,
|
|
30
|
-
headerPrimaryUpper: true,
|
|
31
|
-
hasContainerStyles: true,
|
|
32
|
-
reverseMatrixValues: true,
|
|
33
|
-
|
|
34
|
-
tableContainerStyles: {},
|
|
35
|
-
tableStyles: {},
|
|
36
|
-
|
|
37
|
-
thRowStyles: {},
|
|
38
|
-
thTitleStyles: {},
|
|
39
|
-
thSubTitleStyles: {},
|
|
40
|
-
thPrimaryTitleStyles: {},
|
|
41
|
-
|
|
42
|
-
trRowStyles: {},
|
|
43
|
-
trTitleStyles: {},
|
|
44
|
-
trSubTitleStyles: {},
|
|
45
|
-
trPrimaryTitleStyles: {},
|
|
46
|
-
|
|
47
|
-
tdStyles: {},
|
|
48
|
-
|
|
49
|
-
customHeaderRowIdValue: "",
|
|
50
|
-
customRowDynamicIdValue: "",
|
|
51
|
-
customTableDataDynamicIdValue: "",
|
|
52
|
-
customRowHeaderDynamicIdValue: "",
|
|
53
|
-
customDynamicHeaderTitleIdValue: "",
|
|
54
|
-
customDynamicSubHeaderTitleIdValue: "",
|
|
55
|
-
data,
|
|
56
|
-
}}
|
|
57
|
-
/>
|
|
58
|
-
)
|
|
41
|
+
export function RiskMatrix() {
|
|
42
|
+
return <ReactMatrix data={data} />;
|
|
59
43
|
}
|
|
60
44
|
```
|
|
61
45
|
|
|
62
|
-
|
|
46
|
+
That renders the **Original** design: your own colours from the data, thin black rules, the page's font.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Choosing a design
|
|
51
|
+
|
|
52
|
+
Import a design by name and pass it as `theme`:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
import ReactMatrix, { aurora } from 'react-data-matrix';
|
|
56
|
+
|
|
57
|
+
<ReactMatrix data={data} theme={aurora} />;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Only the designs you import are bundled. To offer a picker, import them all:
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import ReactMatrix, { presets, type PresetName } from 'react-data-matrix';
|
|
64
|
+
|
|
65
|
+
const [name, setName] = useState<PresetName>('boardroom');
|
|
66
|
+
|
|
67
|
+
<>
|
|
68
|
+
<select value={name} onChange={(event) => setName(event.target.value as PresetName)}>
|
|
69
|
+
{Object.entries(presets).map(([key, theme]) => (
|
|
70
|
+
<option key={key} value={key}>
|
|
71
|
+
{theme.name}
|
|
72
|
+
</option>
|
|
73
|
+
))}
|
|
74
|
+
</select>
|
|
75
|
+
<ReactMatrix data={data} theme={presets[name]} />
|
|
76
|
+
</>;
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### The designs
|
|
80
|
+
|
|
81
|
+
Browse them live in the **[gallery](https://bronz3beard.github.io/react-matrix/)**, where you can filter by colour scheme and character.
|
|
82
|
+
|
|
83
|
+
| Design | Look |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `original` | The 0.4.x look: your own data colours, 1px black lines |
|
|
86
|
+
| `aurora` | Northern lights on a night sky: glowing teal, violet, magenta |
|
|
87
|
+
| `midnight` | Deep navy with jewel-tone cells |
|
|
88
|
+
| `broadsheet` | Newspaper financial pages: serif, hairline rules, big figures |
|
|
89
|
+
| `brutal` | Neo-brutalist blocks, thick borders, hard shadows |
|
|
90
|
+
| `glasshouse` | Rounded white-edged cells over a pastel gradient |
|
|
91
|
+
| `terminal` | Phosphor terminal: monospace green on black, outlined cells |
|
|
92
|
+
| `sherbet` | Pill-shaped pastel cells, rounded type, cream background |
|
|
93
|
+
| `graphite` | Greyscale only, prints and photocopies cleanly |
|
|
94
|
+
| `blueprint` | Technical drawing: dashed white lines on blueprint blue |
|
|
95
|
+
| `thermal` | Continuous heatmap by score, edge to edge |
|
|
96
|
+
| `whitespace` | Quiet and spacious, a coloured dot per rating |
|
|
97
|
+
| `boardroom` | Board report: navy header band, left-aligned text |
|
|
98
|
+
| `clay` | Neumorphic tiles pressed out of soft grey |
|
|
99
|
+
| `arcade` | 8-bit cabinet: pixel borders, neon cells |
|
|
100
|
+
| `sunset` | Warm continuous wash by score, sand to crimson |
|
|
101
|
+
| `tidewater` | One hue, many depths: severity as depth of blue |
|
|
102
|
+
| `canopy` | Forest floor: linen, serif, leaf-shaped cells |
|
|
103
|
+
| `fjord` | Nordic dusk: slate surface, muted dots |
|
|
104
|
+
| `neon` | Night-city signage: glowing outlined cells |
|
|
105
|
+
| `ledger` | Accounting ledger: tight grid, monospace figures |
|
|
106
|
+
| `contour` | Line art: cells drawn, not filled |
|
|
107
|
+
| `signal` | Product dashboard: status pills |
|
|
108
|
+
| `beacon` | Accessibility first: colour-blind-safe, AAA contrast |
|
|
109
|
+
| `noir` | Black tie: black card ruled in gold |
|
|
110
|
+
| `swiss` | International typographic style: flush grid, red band |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Styling
|
|
115
|
+
|
|
116
|
+
There are four ways to adjust a design, from smallest to largest change.
|
|
117
|
+
|
|
118
|
+
### 1. Change one value of a design
|
|
119
|
+
|
|
120
|
+
Designs are plain objects, so spread and override:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<ReactMatrix data={data} theme={{ ...aurora, radius: '4px', gap: '0' }} />
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 2. Change one value for one matrix
|
|
127
|
+
|
|
128
|
+
Every design value is a CSS custom property on the matrix, so `style` can override it per instance:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
<ReactMatrix data={data} theme={aurora} style={{ '--rdm-radius': '4px' }} />
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Available variables: `--rdm-font`, `--rdm-font-size`, `--rdm-surface`, `--rdm-backdrop`, `--rdm-text`, `--rdm-muted-text`, `--rdm-header-surface`, `--rdm-header-text`, `--rdm-line`, `--rdm-line-width`, `--rdm-line-style`, `--rdm-radius`, `--rdm-gap`, `--rdm-cell-padding`, `--rdm-cell-shadow`, `--rdm-focus`.
|
|
135
|
+
|
|
136
|
+
### 3. Style individual elements
|
|
137
|
+
|
|
138
|
+
`styles` takes inline styles per element, applied after the design:
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<ReactMatrix
|
|
142
|
+
data={data}
|
|
143
|
+
styles={{
|
|
144
|
+
root: { maxWidth: '60rem' },
|
|
145
|
+
caption: { textAlign: 'start' },
|
|
146
|
+
table: { width: '100%' },
|
|
147
|
+
axisTitle: { letterSpacing: '0.08em' },
|
|
148
|
+
columnHeader: { textTransform: 'none' },
|
|
149
|
+
rowHeader: { minWidth: '9rem' },
|
|
150
|
+
cell: { fontWeight: 700 },
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 4. Write your own design
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import ReactMatrix, { type MatrixTheme } from 'react-data-matrix';
|
|
159
|
+
|
|
160
|
+
const house: MatrixTheme = {
|
|
161
|
+
name: 'House style',
|
|
162
|
+
scheme: 'light',
|
|
163
|
+
font: 'Inter, system-ui, sans-serif',
|
|
164
|
+
fontSize: '0.9375rem',
|
|
165
|
+
surface: '#ffffff',
|
|
166
|
+
text: '#111827',
|
|
167
|
+
mutedText: '#4b5563',
|
|
168
|
+
headerSurface: '#f3f4f6',
|
|
169
|
+
headerText: '#111827',
|
|
170
|
+
line: '#d1d5db',
|
|
171
|
+
lineWidth: '1px',
|
|
172
|
+
lineStyle: 'solid',
|
|
173
|
+
radius: '6px',
|
|
174
|
+
gap: '2px',
|
|
175
|
+
cellPadding: '0.5rem',
|
|
176
|
+
cellShadow: 'none',
|
|
177
|
+
cellVariant: 'fill', // 'fill' | 'outline' | 'chip' | 'dot'
|
|
178
|
+
emphasis: 'label', // 'label' | 'score'
|
|
179
|
+
align: 'center', // 'center' | 'start'
|
|
180
|
+
axisCase: 'upper', // 'upper' | 'none'
|
|
181
|
+
scale: 'tier', // 'tier' = one colour per severity tier, 'score' = heatmap
|
|
182
|
+
focus: '#2563eb',
|
|
183
|
+
palette: [
|
|
184
|
+
{ bg: '#dcfce7', fg: '#14532d' },
|
|
185
|
+
{ bg: '#fef9c3', fg: '#713f12' },
|
|
186
|
+
{ bg: '#ffedd5', fg: '#7c2d12' },
|
|
187
|
+
{ bg: '#fee2e2', fg: '#7f1d1d' },
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
<ReactMatrix data={data} theme={house} />;
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Leave `palette` out to colour cells from your data's own `colour` values, as `original` does.
|
|
195
|
+
|
|
196
|
+
### 5. Use no built-in styling at all
|
|
197
|
+
|
|
198
|
+
`unstyled` renders the semantic markup with stable class names and data attributes, and adds no stylesheet and no variables — useful when you own the design system, or under a strict CSP that forbids inline styles during server rendering.
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
<ReactMatrix data={data} unstyled className="my-matrix" />
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```css
|
|
205
|
+
.my-matrix .rdm-cell[data-tier='0'] { background: #dcfce7; }
|
|
206
|
+
.my-matrix .rdm-cell[data-tier='3'] { background: #fee2e2; }
|
|
207
|
+
.my-matrix .rdm-cell[data-row='A'][data-col='5'] { outline: 2px solid currentColor; }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Class names: `rdm-root`, `rdm-caption`, `rdm-table`, `rdm-axis-title`, `rdm-column-header`, `rdm-row-header`, `rdm-subtitle`, `rdm-cell`, `rdm-cell-label`, `rdm-cell-score`, `rdm-cell-button`.
|
|
211
|
+
Data attributes: `data-row` (likelihood), `data-col` (consequence), `data-tier` (0 = least severe), plus `data-palette`, `data-scheme`, `data-variant`, `data-emphasis`, `data-align` and `data-axis-case` on the root.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Reacting to a chosen cell
|
|
63
216
|
|
|
64
|
-
|
|
65
|
-
|
|
217
|
+
Pass `onCellClick` and each cell becomes a real button: mouse, Enter and Space all work, and each button is named for screen readers, for example "Likelihood Almost Certain, Consequence Catastrophic: extreme (25)".
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
const [selected, setSelected] = useState<MatrixValue | null>(null);
|
|
221
|
+
|
|
222
|
+
<ReactMatrix
|
|
223
|
+
data={data}
|
|
224
|
+
onCellClick={(cell, { row, column, event }) => {
|
|
225
|
+
setSelected(cell);
|
|
226
|
+
console.log(row.row_header_title, column.header_title, cell.score_value, event.type);
|
|
227
|
+
}}
|
|
228
|
+
/>;
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Without `onCellClick`, cells are plain text with no pointer cursor and no tab stops.
|
|
232
|
+
|
|
233
|
+
**Safety.** The matrix renders your data as text only, never as HTML, and colour values from your data are checked before they reach CSS; anything unusual falls back to a neutral colour and is reported to the console. Errors thrown inside your handler are not swallowed. If you render values from the handler yourself, treat them as untrusted data as you would anywhere else.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Data shape
|
|
66
238
|
|
|
67
239
|
```ts
|
|
68
|
-
import { MatrixData } from
|
|
240
|
+
import type { MatrixData } from 'react-data-matrix';
|
|
69
241
|
|
|
70
242
|
export const data: MatrixData = {
|
|
71
243
|
id: 1,
|
|
72
|
-
matrix_size: 5,
|
|
73
|
-
matrix_name:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
244
|
+
matrix_size: 5, // renders a 5×5; 3, 4, 6 … all work
|
|
245
|
+
matrix_name: 'React Matrix', // the table caption
|
|
246
|
+
matrix_description: 'Risk Matrix Template',
|
|
247
|
+
primary_header_title: 'Consequence', // column axis title
|
|
248
|
+
primary_row_header_title: 'Likelihood', // row axis title
|
|
77
249
|
|
|
250
|
+
// One entry per row/column pair: entry i defines column i and row i.
|
|
78
251
|
matrix_details: [
|
|
79
252
|
{
|
|
80
253
|
id: 1,
|
|
81
|
-
likelihood: "E",
|
|
82
|
-
consequence: 1,
|
|
83
254
|
position: 5,
|
|
84
|
-
matrix_type:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
255
|
+
matrix_type: 'Risk',
|
|
256
|
+
likelihood: 'E', // row key
|
|
257
|
+
consequence: 1, // column key
|
|
258
|
+
header_title: 'Minor', // column heading
|
|
259
|
+
header_sub_title: 'Header sub-title/description.',
|
|
260
|
+
row_header_title: 'Rare', // row heading
|
|
261
|
+
row_header_sub_title: 'Row Header sub-title/description.',
|
|
89
262
|
},
|
|
90
|
-
|
|
263
|
+
// …
|
|
91
264
|
],
|
|
92
265
|
|
|
266
|
+
// The ratings. Each one is placed by its own coordinates.
|
|
93
267
|
matrix_values: [
|
|
94
268
|
{
|
|
95
269
|
id: 26,
|
|
96
270
|
matrix_id: 1,
|
|
97
|
-
description:
|
|
271
|
+
description: 'low',
|
|
98
272
|
score_value: 1,
|
|
99
|
-
colour:
|
|
273
|
+
colour: 'green', // used when the design has no palette
|
|
100
274
|
position: 1,
|
|
101
|
-
likelihood_descriptor:
|
|
102
|
-
consequence_descriptor: 1,
|
|
103
|
-
response:
|
|
275
|
+
likelihood_descriptor: 'E', // matches matrix_details.likelihood
|
|
276
|
+
consequence_descriptor: 1, // matches matrix_details.consequence
|
|
277
|
+
response: 'Business as usual',
|
|
104
278
|
},
|
|
105
|
-
|
|
279
|
+
// …
|
|
106
280
|
],
|
|
107
281
|
};
|
|
108
|
-
|
|
109
282
|
```
|
|
110
283
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
position: number;
|
|
117
|
-
matrix_type: string;
|
|
118
|
-
likelihood: string;
|
|
119
|
-
consequence: number;
|
|
120
|
-
header_title: string;
|
|
121
|
-
header_sub_title: string;
|
|
122
|
-
row_header_title: string;
|
|
123
|
-
row_header_sub_title: string;
|
|
124
|
-
}
|
|
284
|
+
- **Order does not matter.** Ratings are placed by their coordinates, not by their position in the array.
|
|
285
|
+
- **Any size.** `matrix_size` decides how many rows and columns are shown.
|
|
286
|
+
- **Missing ratings** leave an empty cell.
|
|
287
|
+
- **Invalid data never crashes the page.** A rating whose coordinates match no row or column is skipped and reported with `console.error`, as are two ratings claiming the same cell.
|
|
288
|
+
- `reverseMatrixValues` (default `true`) puts the most likely row at the top.
|
|
125
289
|
|
|
126
|
-
|
|
127
|
-
id: number;
|
|
128
|
-
matrix_id: number;
|
|
129
|
-
description: string;
|
|
130
|
-
score_value: number;
|
|
131
|
-
colour: string;
|
|
132
|
-
position: number;
|
|
133
|
-
likelihood_descriptor: string;
|
|
134
|
-
consequence_descriptor: number;
|
|
135
|
-
response: string;
|
|
136
|
-
}
|
|
290
|
+
---
|
|
137
291
|
|
|
138
|
-
|
|
139
|
-
id: number;
|
|
140
|
-
matrix_size: number;
|
|
141
|
-
matrix_name: string;
|
|
142
|
-
primary_header_title: string;
|
|
143
|
-
primary_row_header_title: string;
|
|
144
|
-
matrix_description: string;
|
|
145
|
-
matrix_details: MatrixDetail[];
|
|
146
|
-
matrix_values: MatrixValue[];
|
|
147
|
-
}
|
|
292
|
+
## Recipes
|
|
148
293
|
|
|
149
|
-
|
|
150
|
-
data: MatrixData;
|
|
151
|
-
hasTableBorder?: boolean;
|
|
152
|
-
hasInlineStyles?: boolean | undefined;
|
|
153
|
-
hasContainerStyles?: boolean | undefined;
|
|
154
|
-
reverseMatrixValues?: boolean;
|
|
155
|
-
matrixSizeSelected?: number;
|
|
156
|
-
rowPrimaryUpper?: boolean;
|
|
157
|
-
headerPrimaryUpper?: boolean;
|
|
158
|
-
tableContainerStyles?: React.CSSProperties;
|
|
159
|
-
tableStyles?: React.CSSProperties;
|
|
160
|
-
thRowStyles?: React.CSSProperties;
|
|
161
|
-
thTitleStyles?: React.CSSProperties;
|
|
162
|
-
thSubTitleStyles?: React.CSSProperties;
|
|
163
|
-
thPrimaryTitleStyles?: React.CSSProperties;
|
|
164
|
-
trRowStyles?: React.CSSProperties;
|
|
165
|
-
trTitleStyles?: React.CSSProperties;
|
|
166
|
-
trSubTitleStyles?: React.CSSProperties;
|
|
167
|
-
trPrimaryTitleStyles?: React.CSSProperties;
|
|
168
|
-
tdStyles?: React.CSSProperties;
|
|
169
|
-
customHeaderRowIdValue?: string;
|
|
170
|
-
customDynamicHeaderTitleIdValue?: string;
|
|
171
|
-
customDynamicSubHeaderTitleIdValue?: string;
|
|
172
|
-
customRowDynamicIdValue?: string;
|
|
173
|
-
customRowHeaderDynamicIdValue?: string;
|
|
174
|
-
customTableDataDynamicIdValue?: string;
|
|
175
|
-
}
|
|
294
|
+
### Next.js
|
|
176
295
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
hasInlineStyles?: boolean | undefined;
|
|
180
|
-
headerPrimaryUpper?: boolean;
|
|
181
|
-
thRowStyles?: React.CSSProperties;
|
|
182
|
-
thTitleStyles?: React.CSSProperties;
|
|
183
|
-
thSubTitleStyles?: React.CSSProperties;
|
|
184
|
-
thPrimaryTitleStyles?: React.CSSProperties;
|
|
185
|
-
customHeaderRowIdValue?: string;
|
|
186
|
-
customDynamicHeaderTitleIdValue?: string;
|
|
187
|
-
customDynamicSubHeaderTitleIdValue?: string;
|
|
188
|
-
}
|
|
296
|
+
Supported on **Next.js 16 and 15**, App Router and Pages Router. The examples below
|
|
297
|
+
were built and run against Next 16.3.5 and 15.5.25 on React 19.
|
|
189
298
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
reverseMatrixValues?: boolean;
|
|
195
|
-
trRowStyles?: React.CSSProperties;
|
|
196
|
-
trTitleStyles?: React.CSSProperties;
|
|
197
|
-
trSubTitleStyles?: React.CSSProperties;
|
|
198
|
-
trPrimaryTitleStyles?: React.CSSProperties;
|
|
199
|
-
tdStyles?: React.CSSProperties;
|
|
200
|
-
customRowDynamicIdValue?: string;
|
|
201
|
-
customRowHeaderDynamicIdValue?: string;
|
|
202
|
-
customTableDataDynamicIdValue?: string;
|
|
203
|
-
}
|
|
299
|
+
The matrix uses state and effects, so it is a **client component**. That is not the
|
|
300
|
+
same as being client-rendered: it still renders on the server, and the prerendered
|
|
301
|
+
HTML contains the whole table and the design's stylesheet, so there is no blank
|
|
302
|
+
frame before hydration.
|
|
204
303
|
|
|
205
|
-
|
|
206
|
-
data: {
|
|
207
|
-
id: number;
|
|
208
|
-
colour: string;
|
|
209
|
-
position: number;
|
|
210
|
-
matrix_id: number;
|
|
211
|
-
score_value: number;
|
|
212
|
-
description: string;
|
|
213
|
-
response: string;
|
|
214
|
-
likelihood_descriptor: string;
|
|
215
|
-
consequence_descriptor: number;
|
|
216
|
-
};
|
|
217
|
-
tdStyles?: React.CSSProperties;
|
|
218
|
-
hasInlineStyles?: boolean;
|
|
219
|
-
customTableDataDynamicIdValue?: string;
|
|
220
|
-
}
|
|
221
|
-
```
|
|
304
|
+
#### Fetch on the server, render as a client component (App Router)
|
|
222
305
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
hasTableBorder: true,
|
|
226
|
-
rowPrimaryUpper: true,
|
|
227
|
-
hasInlineStyles: true,
|
|
228
|
-
matrixSizeSelected: 5,
|
|
229
|
-
headerPrimaryUpper: true,
|
|
230
|
-
hasContainerStyles: true,
|
|
231
|
-
reverseMatrixValues: true,
|
|
232
|
-
|
|
233
|
-
tableContainerStyles: {},
|
|
234
|
-
tableStyles: {},
|
|
235
|
-
|
|
236
|
-
thRowStyles: {},
|
|
237
|
-
thTitleStyles: {},
|
|
238
|
-
thSubTitleStyles: {},
|
|
239
|
-
thPrimaryTitleStyles: {},
|
|
240
|
-
|
|
241
|
-
trRowStyles: {},
|
|
242
|
-
trTitleStyles: {},
|
|
243
|
-
trSubTitleStyles: {},
|
|
244
|
-
trPrimaryTitleStyles: {},
|
|
245
|
-
|
|
246
|
-
tdStyles: {},
|
|
247
|
-
|
|
248
|
-
customHeaderRowIdValue: "",
|
|
249
|
-
customRowDynamicIdValue: "",
|
|
250
|
-
customTableDataDynamicIdValue: "",
|
|
251
|
-
customRowHeaderDynamicIdValue: "",
|
|
252
|
-
customDynamicHeaderTitleIdValue: "",
|
|
253
|
-
customDynamicSubHeaderTitleIdValue: "",
|
|
254
|
-
};
|
|
255
|
-
```
|
|
306
|
+
The data is a plain object, so it crosses the server/client boundary as a prop. The
|
|
307
|
+
click handler is a function, so it cannot — it belongs in the client component.
|
|
256
308
|
|
|
257
|
-
|
|
309
|
+
```tsx
|
|
310
|
+
// app/matrix.tsx
|
|
311
|
+
'use client';
|
|
258
312
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
- If false all inline styles are removed.
|
|
313
|
+
import { useState } from 'react';
|
|
314
|
+
import ReactMatrix, { boardroom, type MatrixData } from 'react-data-matrix';
|
|
262
315
|
|
|
263
|
-
|
|
316
|
+
export default function Matrix({ data }: { data: MatrixData }) {
|
|
317
|
+
const [chosen, setChosen] = useState('Choose a cell.');
|
|
264
318
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
319
|
+
return (
|
|
320
|
+
<>
|
|
321
|
+
<ReactMatrix
|
|
322
|
+
data={data}
|
|
323
|
+
theme={boardroom}
|
|
324
|
+
onCellClick={(cell, { row, column }) =>
|
|
325
|
+
setChosen(`${row.row_header_title} × ${column.header_title}: ${cell.description}`)
|
|
326
|
+
}
|
|
327
|
+
/>
|
|
328
|
+
<p role="status">{chosen}</p>
|
|
329
|
+
</>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
```
|
|
268
333
|
|
|
269
|
-
|
|
334
|
+
```tsx
|
|
335
|
+
// app/page.tsx — a Server Component: nothing here reaches the browser
|
|
336
|
+
import Matrix from './matrix';
|
|
270
337
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
338
|
+
export default async function Page() {
|
|
339
|
+
const data = await getRiskMatrix();
|
|
340
|
+
return <Matrix data={data} />;
|
|
341
|
+
}
|
|
342
|
+
```
|
|
274
343
|
|
|
275
|
-
|
|
344
|
+
#### Static sites
|
|
276
345
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
- When true the lowest value will be in the bottom left and the highest value will be in the top right, with all sequential values in-between.
|
|
280
|
-
- When false the values will be reversed, lowest value top left highest value bottom right.
|
|
346
|
+
Nothing about the matrix forces a server. With `output: 'export'` the page is
|
|
347
|
+
prerendered at build time and the exported HTML contains the full matrix.
|
|
281
348
|
|
|
282
|
-
|
|
349
|
+
#### Pages Router
|
|
283
350
|
|
|
284
|
-
|
|
285
|
-
- Default is true.
|
|
286
|
-
- If false string formatting will be removed.
|
|
351
|
+
The same component, with the router's own data fetching. No extra configuration.
|
|
287
352
|
|
|
288
|
-
|
|
353
|
+
```tsx
|
|
354
|
+
// pages/index.tsx
|
|
355
|
+
import type { GetServerSideProps } from 'next';
|
|
356
|
+
import ReactMatrix, { boardroom, type MatrixData } from 'react-data-matrix';
|
|
289
357
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
358
|
+
export const getServerSideProps: GetServerSideProps<{ matrix: MatrixData }> = async () => ({
|
|
359
|
+
props: { matrix: await getRiskMatrix() },
|
|
360
|
+
});
|
|
293
361
|
|
|
294
|
-
|
|
362
|
+
export default function Home({ matrix }: { matrix: MatrixData }) {
|
|
363
|
+
return <ReactMatrix data={matrix} theme={boardroom} />;
|
|
364
|
+
}
|
|
365
|
+
```
|
|
295
366
|
|
|
296
|
-
|
|
297
|
-
- Default is empty string.
|
|
298
|
-
- can be used to target element through id prop.
|
|
367
|
+
#### A strict Content Security Policy
|
|
299
368
|
|
|
300
|
-
|
|
369
|
+
Generate a nonce per request and pass it to the matrix. **Next 16** uses `proxy.ts`;
|
|
370
|
+
**Next 15** uses `middleware.ts` with a named `middleware` export — that difference is
|
|
371
|
+
Next's own, not this package's.
|
|
301
372
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
373
|
+
```ts
|
|
374
|
+
// proxy.ts (Next 16) — middleware.ts on Next 15
|
|
375
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
305
376
|
|
|
306
|
-
|
|
377
|
+
export default function proxy(request: NextRequest) {
|
|
378
|
+
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
|
|
379
|
+
const headers = new Headers(request.headers);
|
|
380
|
+
headers.set('x-nonce', nonce);
|
|
307
381
|
|
|
308
|
-
|
|
309
|
-
-
|
|
310
|
-
|
|
382
|
+
const response = NextResponse.next({ request: { headers } });
|
|
383
|
+
response.headers.set('Content-Security-Policy', `style-src 'nonce-${nonce}'`);
|
|
384
|
+
return response;
|
|
385
|
+
}
|
|
386
|
+
```
|
|
311
387
|
|
|
312
|
-
|
|
388
|
+
```tsx
|
|
389
|
+
// app/page.tsx
|
|
390
|
+
import { headers } from 'next/headers';
|
|
313
391
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
392
|
+
export default async function Page() {
|
|
393
|
+
const nonce = (await headers()).get('x-nonce') ?? undefined;
|
|
394
|
+
return <Matrix data={await getRiskMatrix()} nonce={nonce} />;
|
|
395
|
+
}
|
|
396
|
+
```
|
|
317
397
|
|
|
318
|
-
|
|
398
|
+
The page then opts out of static rendering, because it reads a per-request header.
|
|
319
399
|
|
|
320
|
-
|
|
321
|
-
- Default is empty string.
|
|
322
|
-
- can be used to target element through id prop.
|
|
400
|
+
### Server rendering with a strict CSP (other frameworks)
|
|
323
401
|
|
|
324
|
-
|
|
402
|
+
Pass the page's nonce so the matrix's stylesheet is allowed:
|
|
325
403
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
404
|
+
```tsx
|
|
405
|
+
<ReactMatrix data={data} theme={aurora} nonce={nonce} />
|
|
406
|
+
```
|
|
329
407
|
|
|
330
|
-
|
|
408
|
+
On **React 19**, server rendering also needs the same nonce as a render option, because React manages the hoisted stylesheet:
|
|
331
409
|
|
|
332
|
-
|
|
410
|
+
```ts
|
|
411
|
+
renderToPipeableStream(<App />, { nonce: { style: nonce } });
|
|
412
|
+
```
|
|
333
413
|
|
|
334
|
-
|
|
414
|
+
Client-rendered apps need only the `nonce` prop: the matrix applies its design through the CSSOM, which a `style-src` policy does not restrict. Alternatively use `unstyled` and bring your own stylesheet.
|
|
335
415
|
|
|
336
|
-
|
|
337
|
-
if you make `hasInlineStyles = false`, then add these values with your own styles, you can have full control over styling inline.**
|
|
416
|
+
### Several matrices on one page
|
|
338
417
|
|
|
339
|
-
|
|
418
|
+
Render as many as you like; on React 19 they share a single stylesheet, hoisted into `<head>` and loaded once.
|
|
340
419
|
|
|
341
|
-
|
|
342
|
-
|
|
420
|
+
```tsx
|
|
421
|
+
<>
|
|
422
|
+
<ReactMatrix data={corporate} theme={boardroom} />
|
|
423
|
+
<ReactMatrix data={project} theme={boardroom} />
|
|
424
|
+
</>
|
|
425
|
+
```
|
|
343
426
|
|
|
344
|
-
|
|
427
|
+
### Following the operating system's dark mode
|
|
345
428
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
- thSubTitleStyles={{}}
|
|
349
|
-
- thPrimaryTitleStyles={{}}
|
|
429
|
+
```tsx
|
|
430
|
+
import ReactMatrix, { whitespace, midnight } from 'react-data-matrix';
|
|
350
431
|
|
|
351
|
-
|
|
432
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
352
433
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
- trSubTitleStyles={{}},
|
|
356
|
-
- trPrimaryTitleStyles={{}},
|
|
434
|
+
<ReactMatrix data={data} theme={prefersDark ? midnight : whitespace} />;
|
|
435
|
+
```
|
|
357
436
|
|
|
358
|
-
|
|
437
|
+
### A smaller matrix
|
|
359
438
|
|
|
360
|
-
|
|
439
|
+
```tsx
|
|
440
|
+
<ReactMatrix data={{ ...data, matrix_size: 3 }} />
|
|
441
|
+
```
|
|
361
442
|
|
|
362
443
|
---
|
|
363
444
|
|
|
364
|
-
|
|
445
|
+
## Accessibility
|
|
446
|
+
|
|
447
|
+
- A real `<table>` with a `<caption>`, column and row header scopes, and axis titles spanning their group.
|
|
448
|
+
- With `onCellClick`, cells are native buttons: reachable by keyboard, activated by Enter or Space, and named by row, column and rating.
|
|
449
|
+
- A matrix wider than its container scrolls inside itself, and becomes a keyboard-scrollable labelled region only while it overflows, so it never adds needless tab stops.
|
|
450
|
+
- Sub-titles hide and padding tightens automatically in narrow containers.
|
|
451
|
+
- Every built-in design is contrast-checked; `beacon` targets AAA with a colour-blind-safe palette; `graphite` works in greyscale and print.
|
|
452
|
+
- High-contrast mode (`forced-colors`) keeps cell borders and focus rings visible.
|
|
453
|
+
- **Your data's own colours are your responsibility.** With designs that have no palette (such as `original`), cell colours come from your data and may not meet contrast requirements; use a palette design, or choose accessible colours.
|
|
454
|
+
|
|
455
|
+
## Package facts
|
|
456
|
+
|
|
457
|
+
| | |
|
|
458
|
+
| --- | --- |
|
|
459
|
+
| Format | ESM only |
|
|
460
|
+
| Runtime dependencies | none |
|
|
461
|
+
| Peer dependencies | react, react-dom (`^18.3` or `^19`) |
|
|
462
|
+
| Types | included |
|
|
463
|
+
| Size | ~5 kB gzipped with one design; ~8 kB with all 26 |
|
|
464
|
+
| Contents | the built module, its types, the readme and the licence |
|
|
465
|
+
|
|
466
|
+
## Migrating from 0.4.x
|
|
467
|
+
|
|
468
|
+
The 1.0 API replaces per-element style props with designs.
|
|
469
|
+
|
|
470
|
+
| 0.4.x | 1.0 |
|
|
471
|
+
| --- | --- |
|
|
472
|
+
| `hasInlineStyles={false}` | `unstyled` |
|
|
473
|
+
| `hasTableBorder`, `hasContainerStyles` | design values, or `styles.table` / `styles.root` |
|
|
474
|
+
| `tableContainerStyles` | `styles.root` |
|
|
475
|
+
| `tableStyles` | `styles.table` |
|
|
476
|
+
| `thRowStyles`, `thTitleStyles`, `thSubTitleStyles`, `thPrimaryTitleStyles` | `styles.columnHeader`, `styles.axisTitle` |
|
|
477
|
+
| `trRowStyles`, `trTitleStyles`, `trSubTitleStyles`, `trPrimaryTitleStyles` | `styles.rowHeader`, `styles.axisTitle` |
|
|
478
|
+
| `tdStyles` | `styles.cell` |
|
|
479
|
+
| `rowPrimaryUpper`, `headerPrimaryUpper` | design value `axisCase` |
|
|
480
|
+
| `customHeaderRowIdValue` and the other `custom*IdValue` props | `rdm-*` class names and `data-row` / `data-col` / `data-tier` |
|
|
481
|
+
| `matrixSizeSelected` | removed; use `data.matrix_size` |
|
|
482
|
+
| clicking a cell showed an `alert()` | `onCellClick`, or nothing at all |
|
|
483
|
+
|
|
484
|
+
Also new in 1.0: `react` and `react-dom` are declared peer dependencies, the package is ESM-only, and the exported types include `MatrixTheme`, `CellClickContext`, `MatrixSlot` and `PresetName`.
|
|
485
|
+
|
|
486
|
+
## Contributing
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
npm ci
|
|
490
|
+
npm run dev # demo site
|
|
491
|
+
npm test # unit tests
|
|
492
|
+
npm run test:e2e:docker # browser tests in the pinned Playwright image
|
|
493
|
+
npm run lint && npm run typecheck && npm run build && npm run check:package
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
Pull request guidance is in [docs/pull_request_template.md](docs/pull_request_template.md).
|
|
497
|
+
|
|
498
|
+
## Licence
|
|
499
|
+
|
|
500
|
+
MIT © [bronz3beard](https://www.heyrory.com/)
|