react-data-matrix 0.4.2 → 1.0.0-rc.1

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.
Files changed (52) hide show
  1. package/README.md +416 -280
  2. package/dist/components/MatrixHeaders.d.ts +8 -0
  3. package/dist/components/MatrixRows.d.ts +16 -0
  4. package/dist/components/TableData.d.ts +17 -0
  5. package/dist/grid.d.ts +13 -0
  6. package/dist/index.d.ts +6 -0
  7. package/dist/index.js +1414 -0
  8. package/dist/theme/baseCss.d.ts +2 -0
  9. package/dist/theme/colour.d.ts +10 -0
  10. package/dist/theme/presets/arcade.d.ts +2 -0
  11. package/dist/theme/presets/aurora.d.ts +2 -0
  12. package/dist/theme/presets/beacon.d.ts +2 -0
  13. package/dist/theme/presets/blueprint.d.ts +2 -0
  14. package/dist/theme/presets/boardroom.d.ts +2 -0
  15. package/dist/theme/presets/broadsheet.d.ts +2 -0
  16. package/dist/theme/presets/brutal.d.ts +2 -0
  17. package/dist/theme/presets/canopy.d.ts +2 -0
  18. package/dist/theme/presets/clay.d.ts +2 -0
  19. package/dist/theme/presets/contour.d.ts +2 -0
  20. package/dist/theme/presets/fjord.d.ts +2 -0
  21. package/dist/theme/presets/glasshouse.d.ts +2 -0
  22. package/dist/theme/presets/graphite.d.ts +2 -0
  23. package/dist/theme/presets/index.d.ts +57 -0
  24. package/dist/theme/presets/ledger.d.ts +2 -0
  25. package/dist/theme/presets/midnight.d.ts +2 -0
  26. package/dist/theme/presets/neon.d.ts +2 -0
  27. package/dist/theme/presets/noir.d.ts +2 -0
  28. package/dist/theme/presets/original.d.ts +2 -0
  29. package/dist/theme/presets/sherbet.d.ts +2 -0
  30. package/dist/theme/presets/signal.d.ts +2 -0
  31. package/dist/theme/presets/sunset.d.ts +2 -0
  32. package/dist/theme/presets/swiss.d.ts +2 -0
  33. package/dist/theme/presets/terminal.d.ts +2 -0
  34. package/dist/theme/presets/thermal.d.ts +2 -0
  35. package/dist/theme/presets/tidewater.d.ts +2 -0
  36. package/dist/theme/presets/whitespace.d.ts +2 -0
  37. package/dist/theme/severity.d.ts +18 -0
  38. package/dist/theme/tokens.d.ts +11 -0
  39. package/dist/theme/types.d.ts +36 -0
  40. package/dist/types/index.d.ts +71 -0
  41. package/package.json +50 -37
  42. package/dist/lib/components/MatrixHeaders.d.ts +0 -4
  43. package/dist/lib/components/MatrixRows.d.ts +0 -4
  44. package/dist/lib/components/TableData.d.ts +0 -4
  45. package/dist/lib/helpers/getStyles.d.ts +0 -12
  46. package/dist/lib/index.d.ts +0 -4
  47. package/dist/lib/types/index.d.ts +0 -102
  48. package/dist/lib/utils/data.d.ts +0 -2
  49. package/dist/lib/utils/functions.d.ts +0 -4
  50. package/dist/manifest.json +0 -7
  51. package/dist/react-data-matrix.es.js +0 -913
  52. package/dist/react-data-matrix.umd.js +0 -28
package/README.md CHANGED
@@ -1,364 +1,500 @@
1
- # React Matrix
1
+ # react-data-matrix
2
2
 
3
- [React Matrix Demo](https://bronz3beard.github.io/react-matrix/)
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
- React Matrix table shows relationships between two or more variables in a data set in grid format.
5
+ **[See all 26 designs in the live gallery →](https://bronz3beard.github.io/react-matrix/)**
6
6
 
7
- The most common usage for a table like React Matrix, is to display the **likelihood** and **consequence** "scores" of **risks/hazards**, this can be for anything from Corporate, Work Health Safety and Environment risks, and more.
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
- # PR's
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
- - Have a look at the [PR template doc](https://github.com/bronz3beard/react-matrix/blob/main/docs) for best approach to getting your pr merged.
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
- # Usage
32
+ ## Quick start
14
33
 
15
34
  ```tsx
16
- import ReactMatrix from "react-data-matrix";
35
+ import ReactMatrix, { type MatrixData } from 'react-data-matrix';
17
36
 
18
- const App: FC = () => {
19
- ...
37
+ const data: MatrixData = {
38
+ /* see Data shape below */
39
+ };
20
40
 
21
- return (
22
- <ReactMatrix
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
- # Expected Data Structure
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
- - See [`./src/lib/utils/data.ts`](https://github.com/bronz3beard/react-matrix/blob/main/src/lib/utils/data.ts) for an example of the data, this can be used for testing if needed,
65
- or an example of how to construct/deconstruct your data objects from your api.
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 "react-data-matrix";
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: "React Matrix",
74
- primary_header_title: "Consequence",
75
- primary_row_header_title: "Likelihood",
76
- matrix_description: "Risk Matrix Template",
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: "Risk",
85
- header_title: "Minor",
86
- header_sub_title: "Header sub-title/description.",
87
- row_header_title: "Rare",
88
- row_header_sub_title: "Row Header sub-title/description.",
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: "low",
271
+ description: 'low',
98
272
  score_value: 1,
99
- colour: "green",
273
+ colour: 'green', // used when the design has no palette
100
274
  position: 1,
101
- likelihood_descriptor: "E",
102
- consequence_descriptor: 1,
103
- response: "Business as usual",
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
- # PropTypes
112
-
113
- ```ts
114
- export interface MatrixDetail {
115
- id: number;
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
- export interface MatrixValue {
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
- export interface MatrixData {
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
- export interface ReactMatrixProps {
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
- export interface MatrixHeaderProps {
178
- data: MatrixData;
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
- export interface MatrixRowsProps {
191
- data: MatrixData;
192
- rowPrimaryUpper?: boolean;
193
- hasInlineStyles?: boolean | undefined;
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
- export interface TableDataProps {
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
- ```ts
224
- defaultProps = {
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
- # Available Properties
309
+ ```tsx
310
+ // app/matrix.tsx
311
+ 'use client';
258
312
 
259
- - **hasInlineStyles**
260
- - Default is true.
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
- - **hasTableBorder**
266
- - Default is true.
267
- - If false the most outer border is removed.
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
- - **hasContainerStyles**
272
- - Default is true.
273
- - This will center the matrix in the middle of the page.
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
- - **reverseMatrixValues**
278
- - Default is true.
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
- - **rowPrimaryUpper**
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
- - **headerPrimaryUpper**
291
- - Default is true.
292
- - If false string formatting will be removed.
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
- - **customHeaderRowIdValue**
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
- - **customDynamicHeaderTitleIdValue**
303
- - Default is empty string.
304
- - can be used to target element through id prop.
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
- - **customDynamicSubHeaderTitleIdValue**
309
- - Default is empty string.
310
- - can be used to target element through id prop.
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
- - **customRowDynamicIdValue**
315
- - Default is empty string.
316
- - can be used to target element through id prop.
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
- - **customRowHeaderDynamicIdValue**
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
- - **customTableDataDynamicIdValue**
327
- - Default is empty string.
328
- - can be used to target element through id prop.
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
- # Styling Properties
410
+ ```ts
411
+ renderToPipeableStream(<App />, { nonce: { style: nonce } });
412
+ ```
333
413
 
334
- ### Inline
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
- **These styles can be added to the current styles or,
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
- **ReactMatrix**
418
+ Render as many as you like; on React 19 they share a single stylesheet, hoisted into `<head>` and loaded once.
340
419
 
341
- - tableContainerStyles={{}}
342
- - tableStyles={{}}
420
+ ```tsx
421
+ <>
422
+ <ReactMatrix data={corporate} theme={boardroom} />
423
+ <ReactMatrix data={project} theme={boardroom} />
424
+ </>
425
+ ```
343
426
 
344
- **MatrixHeaders**
427
+ ### Following the operating system's dark mode
345
428
 
346
- - thRowStyles={{}}
347
- - thTitleStyles={{}}
348
- - thSubTitleStyles={{}}
349
- - thPrimaryTitleStyles={{}}
429
+ ```tsx
430
+ import ReactMatrix, { whitespace, midnight } from 'react-data-matrix';
350
431
 
351
- **MatrixRows**
432
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
352
433
 
353
- - trRowStyles={{}},
354
- - trTitleStyles={{}},
355
- - trSubTitleStyles={{}},
356
- - trPrimaryTitleStyles={{}},
434
+ <ReactMatrix data={data} theme={prefersDark ? midnight : whitespace} />;
435
+ ```
357
436
 
358
- **TableData**
437
+ ### A smaller matrix
359
438
 
360
- - tdStyles={{}},
439
+ ```tsx
440
+ <ReactMatrix data={{ ...data, matrix_size: 3 }} />
441
+ ```
361
442
 
362
443
  ---
363
444
 
364
- # React + TypeScript + Vite
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/)