@instructure/ui-table 10.2.1 → 10.2.2-pr-snapshot-1726164252153
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/CHANGELOG.md +8 -0
- package/package.json +17 -17
- package/src/Table/README.md +1626 -681
- package/tsconfig.build.tsbuildinfo +1 -1
package/src/Table/README.md
CHANGED
@@ -11,33 +11,112 @@ In stacked layout, column header is rendered in each cell, but not in row header
|
|
11
11
|
> exceed the bounds of the table cell, use `fixed` or `stacked`, together with the [Text](#Text) component:
|
12
12
|
> `<Text wrap="break-word">[long string]</Text>`.
|
13
13
|
|
14
|
-
```javascript
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
layout: 'auto',
|
21
|
-
hover: false,
|
22
|
-
}
|
14
|
+
- ```javascript
|
15
|
+
class Example extends React.Component {
|
16
|
+
state = {
|
17
|
+
layout: 'auto',
|
18
|
+
hover: false
|
19
|
+
}
|
23
20
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
handleChange = (field, value) => {
|
22
|
+
this.setState({
|
23
|
+
[field]: value
|
24
|
+
})
|
25
|
+
}
|
26
|
+
|
27
|
+
renderOptions() {
|
28
|
+
const { layout, hover } = this.state
|
29
|
+
|
30
|
+
return (
|
31
|
+
<Flex alignItems="start">
|
32
|
+
<Flex.Item margin="small">
|
33
|
+
<RadioInputGroup
|
34
|
+
name="layout"
|
35
|
+
description="layout"
|
36
|
+
value={layout}
|
37
|
+
onChange={(e, value) => this.handleChange('layout', value)}
|
38
|
+
>
|
39
|
+
<RadioInput label="auto" value="auto" />
|
40
|
+
<RadioInput label="fixed" value="fixed" />
|
41
|
+
<RadioInput label="stacked" value="stacked" />
|
42
|
+
</RadioInputGroup>
|
43
|
+
</Flex.Item>
|
44
|
+
<Flex.Item margin="small">
|
45
|
+
<Checkbox
|
46
|
+
label="hover"
|
47
|
+
checked={hover}
|
48
|
+
onChange={(e, value) => this.handleChange('hover', !hover)}
|
49
|
+
/>
|
50
|
+
</Flex.Item>
|
51
|
+
</Flex>
|
52
|
+
)
|
53
|
+
}
|
54
|
+
|
55
|
+
render() {
|
56
|
+
const { layout, hover } = this.state
|
57
|
+
|
58
|
+
return (
|
59
|
+
<div>
|
60
|
+
{this.renderOptions()}
|
61
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
62
|
+
<Table.Head>
|
63
|
+
<Table.Row>
|
64
|
+
<Table.ColHeader id="Rank">Rank</Table.ColHeader>
|
65
|
+
<Table.ColHeader id="Title">Title</Table.ColHeader>
|
66
|
+
<Table.ColHeader id="Year">Year</Table.ColHeader>
|
67
|
+
<Table.ColHeader id="Rating">Rating</Table.ColHeader>
|
68
|
+
</Table.Row>
|
69
|
+
</Table.Head>
|
70
|
+
<Table.Body>
|
71
|
+
<Table.Row>
|
72
|
+
<Table.RowHeader>1</Table.RowHeader>
|
73
|
+
<Table.Cell>The Shawshank Redemption</Table.Cell>
|
74
|
+
<Table.Cell>1994</Table.Cell>
|
75
|
+
<Table.Cell>9.3</Table.Cell>
|
76
|
+
</Table.Row>
|
77
|
+
<Table.Row>
|
78
|
+
<Table.RowHeader>2</Table.RowHeader>
|
79
|
+
<Table.Cell>The Godfather</Table.Cell>
|
80
|
+
<Table.Cell>1972</Table.Cell>
|
81
|
+
<Table.Cell>9.2</Table.Cell>
|
82
|
+
</Table.Row>
|
83
|
+
<Table.Row>
|
84
|
+
<Table.RowHeader>3</Table.RowHeader>
|
85
|
+
<Table.Cell>The Godfather: Part II</Table.Cell>
|
86
|
+
<Table.Cell>1974</Table.Cell>
|
87
|
+
<Table.Cell>9.0</Table.Cell>
|
88
|
+
</Table.Row>
|
89
|
+
</Table.Body>
|
90
|
+
</Table>
|
91
|
+
</div>
|
92
|
+
)
|
93
|
+
}
|
28
94
|
}
|
29
95
|
|
30
|
-
|
31
|
-
|
96
|
+
render(<Example />)
|
97
|
+
```
|
32
98
|
|
33
|
-
|
99
|
+
- ```javascript
|
100
|
+
const Example = () => {
|
101
|
+
const [layout, setLayout] = useState('auto')
|
102
|
+
const [hover, setHover] = useState(false)
|
103
|
+
|
104
|
+
const handleChange = (field, value) => {
|
105
|
+
if (field === 'layout') {
|
106
|
+
setLayout(value)
|
107
|
+
} else if (field === 'hover') {
|
108
|
+
setHover(value)
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
const renderOptions = () => (
|
34
113
|
<Flex alignItems="start">
|
35
114
|
<Flex.Item margin="small">
|
36
115
|
<RadioInputGroup
|
37
116
|
name="layout"
|
38
117
|
description="layout"
|
39
118
|
value={layout}
|
40
|
-
onChange={(e, value) =>
|
119
|
+
onChange={(e, value) => handleChange('layout', value)}
|
41
120
|
>
|
42
121
|
<RadioInput label="auto" value="auto" />
|
43
122
|
<RadioInput label="fixed" value="fixed" />
|
@@ -48,24 +127,16 @@ class Example extends React.Component {
|
|
48
127
|
<Checkbox
|
49
128
|
label="hover"
|
50
129
|
checked={hover}
|
51
|
-
onChange={(e, value) =>
|
130
|
+
onChange={(e, value) => handleChange('hover', !hover)}
|
52
131
|
/>
|
53
132
|
</Flex.Item>
|
54
133
|
</Flex>
|
55
134
|
)
|
56
|
-
}
|
57
|
-
|
58
|
-
render() {
|
59
|
-
const { layout, hover } = this.state
|
60
135
|
|
61
136
|
return (
|
62
137
|
<div>
|
63
|
-
{
|
64
|
-
<Table
|
65
|
-
caption='Top rated movies'
|
66
|
-
layout={layout}
|
67
|
-
hover={hover}
|
68
|
-
>
|
138
|
+
{renderOptions()}
|
139
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
69
140
|
<Table.Head>
|
70
141
|
<Table.Row>
|
71
142
|
<Table.ColHeader id="Rank">Rank</Table.ColHeader>
|
@@ -98,41 +169,154 @@ class Example extends React.Component {
|
|
98
169
|
</div>
|
99
170
|
)
|
100
171
|
}
|
101
|
-
}
|
102
172
|
|
103
|
-
render(<Example />)
|
104
|
-
```
|
173
|
+
render(<Example />)
|
174
|
+
```
|
105
175
|
|
106
176
|
### Column width and alignment
|
107
177
|
|
108
178
|
Each column (`ColHeader`) can have a custom width, and each cell (`ColHeader`, `RowHeader` or `Cell`)
|
109
179
|
can be aligned differently.
|
110
180
|
|
111
|
-
```javascript
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
181
|
+
- ```javascript
|
182
|
+
class Example extends React.Component {
|
183
|
+
render() {
|
184
|
+
const { headers, rows } = this.props
|
185
|
+
|
186
|
+
return (
|
187
|
+
<Responsive
|
188
|
+
query={{
|
189
|
+
small: { maxWidth: '40rem' },
|
190
|
+
large: { minWidth: '41rem' }
|
191
|
+
}}
|
192
|
+
props={{
|
193
|
+
small: { layout: 'stacked' },
|
194
|
+
large: { layout: 'fixed' }
|
195
|
+
}}
|
196
|
+
>
|
197
|
+
{({ layout }) => (
|
198
|
+
<div>
|
199
|
+
<Table caption="Top rated movies" layout={layout}>
|
200
|
+
<Table.Head>
|
201
|
+
<Table.Row>
|
202
|
+
{(headers || []).map(({ id, text, width, textAlign }) => (
|
203
|
+
<Table.ColHeader
|
204
|
+
key={id}
|
205
|
+
id={id}
|
206
|
+
width={width}
|
207
|
+
textAlign={textAlign}
|
208
|
+
>
|
209
|
+
{text}
|
210
|
+
</Table.ColHeader>
|
211
|
+
))}
|
212
|
+
</Table.Row>
|
213
|
+
</Table.Head>
|
214
|
+
<Table.Body>
|
215
|
+
{rows.map((row) => (
|
216
|
+
<Table.Row key={row.id}>
|
217
|
+
{headers.map(({ id, renderCell, textAlign }) => (
|
218
|
+
<Table.Cell
|
219
|
+
key={id}
|
220
|
+
textAlign={layout === 'stacked' ? 'start' : textAlign}
|
221
|
+
>
|
222
|
+
{renderCell ? renderCell(row[id], layout) : row[id]}
|
223
|
+
</Table.Cell>
|
224
|
+
))}
|
225
|
+
</Table.Row>
|
226
|
+
))}
|
227
|
+
</Table.Body>
|
228
|
+
</Table>
|
229
|
+
</div>
|
230
|
+
)}
|
231
|
+
</Responsive>
|
232
|
+
)
|
233
|
+
}
|
234
|
+
}
|
118
235
|
|
236
|
+
const renderSummary = (summary, layout) =>
|
237
|
+
layout === 'stacked' ? (
|
238
|
+
summary
|
239
|
+
) : (
|
240
|
+
<TruncateText truncate="word" ellipsis="...">
|
241
|
+
{summary}
|
242
|
+
</TruncateText>
|
243
|
+
)
|
244
|
+
|
245
|
+
render(
|
246
|
+
<Example
|
247
|
+
headers={[
|
248
|
+
{
|
249
|
+
id: 'Title',
|
250
|
+
text: 'Title',
|
251
|
+
width: '25%',
|
252
|
+
textAlign: 'start'
|
253
|
+
},
|
254
|
+
{
|
255
|
+
id: 'Year',
|
256
|
+
text: 'Year',
|
257
|
+
width: '15%',
|
258
|
+
textAlign: 'start'
|
259
|
+
},
|
260
|
+
{
|
261
|
+
id: 'Summary',
|
262
|
+
text: 'Summary',
|
263
|
+
width: '40%',
|
264
|
+
renderCell: renderSummary,
|
265
|
+
textAlign: 'start'
|
266
|
+
},
|
267
|
+
{
|
268
|
+
id: 'BoxOffice',
|
269
|
+
text: 'Box Office',
|
270
|
+
width: '20%',
|
271
|
+
textAlign: 'end'
|
272
|
+
}
|
273
|
+
]}
|
274
|
+
rows={[
|
275
|
+
{
|
276
|
+
id: '1',
|
277
|
+
Title: 'The Shawshank Redemption',
|
278
|
+
Year: 1994,
|
279
|
+
Summary:
|
280
|
+
'Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.',
|
281
|
+
BoxOffice: '$28,341,469'
|
282
|
+
},
|
283
|
+
{
|
284
|
+
id: '2',
|
285
|
+
Title: 'The Godfather',
|
286
|
+
Year: 1972,
|
287
|
+
Summary:
|
288
|
+
'The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.',
|
289
|
+
BoxOffice: '$133,698,921'
|
290
|
+
},
|
291
|
+
{
|
292
|
+
id: '3',
|
293
|
+
Title: 'The Godfather: Part II',
|
294
|
+
Year: 1974,
|
295
|
+
Summary:
|
296
|
+
'The early life and career of Vito Corleone in 1920s New York City is portrayed, while his son, Michael, expands and tightens his grip on the family crime syndicate.',
|
297
|
+
BoxOffice: '$47,542,841'
|
298
|
+
}
|
299
|
+
]}
|
300
|
+
/>
|
301
|
+
)
|
302
|
+
```
|
303
|
+
|
304
|
+
- ```javascript
|
305
|
+
const Example = ({ headers, rows }) => {
|
119
306
|
return (
|
120
307
|
<Responsive
|
121
308
|
query={{
|
122
309
|
small: { maxWidth: '40rem' },
|
123
|
-
large: { minWidth: '41rem' }
|
310
|
+
large: { minWidth: '41rem' }
|
124
311
|
}}
|
125
312
|
props={{
|
126
313
|
small: { layout: 'stacked' },
|
127
|
-
large: { layout: 'fixed' }
|
314
|
+
large: { layout: 'fixed' }
|
128
315
|
}}
|
129
316
|
>
|
130
317
|
{({ layout }) => (
|
131
318
|
<div>
|
132
|
-
<Table
|
133
|
-
caption='Top rated movies'
|
134
|
-
layout={layout}
|
135
|
-
>
|
319
|
+
<Table caption="Top rated movies" layout={layout}>
|
136
320
|
<Table.Head>
|
137
321
|
<Table.Row>
|
138
322
|
{(headers || []).map(({ id, text, width, textAlign }) => (
|
@@ -144,7 +328,7 @@ class Example extends React.Component {
|
|
144
328
|
>
|
145
329
|
{text}
|
146
330
|
</Table.ColHeader>
|
147
|
-
|
331
|
+
))}
|
148
332
|
</Table.Row>
|
149
333
|
</Table.Head>
|
150
334
|
<Table.Body>
|
@@ -167,74 +351,74 @@ class Example extends React.Component {
|
|
167
351
|
</Responsive>
|
168
352
|
)
|
169
353
|
}
|
170
|
-
}
|
171
|
-
|
172
|
-
const renderSummary = (summary, layout) => (layout === 'stacked')
|
173
|
-
? summary
|
174
|
-
: (
|
175
|
-
<TruncateText
|
176
|
-
truncate="word"
|
177
|
-
ellipsis="..."
|
178
|
-
>
|
179
|
-
{summary}
|
180
|
-
</TruncateText>
|
181
|
-
)
|
182
354
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
355
|
+
const renderSummary = (summary, layout) =>
|
356
|
+
layout === 'stacked' ? (
|
357
|
+
summary
|
358
|
+
) : (
|
359
|
+
<TruncateText truncate="word" ellipsis="...">
|
360
|
+
{summary}
|
361
|
+
</TruncateText>
|
362
|
+
)
|
363
|
+
|
364
|
+
render(
|
365
|
+
<Example
|
366
|
+
headers={[
|
367
|
+
{
|
368
|
+
id: 'Title',
|
369
|
+
text: 'Title',
|
370
|
+
width: '25%',
|
371
|
+
textAlign: 'start'
|
372
|
+
},
|
373
|
+
{
|
374
|
+
id: 'Year',
|
375
|
+
text: 'Year',
|
376
|
+
width: '15%',
|
377
|
+
textAlign: 'start'
|
378
|
+
},
|
379
|
+
{
|
380
|
+
id: 'Summary',
|
381
|
+
text: 'Summary',
|
382
|
+
width: '40%',
|
383
|
+
renderCell: renderSummary,
|
384
|
+
textAlign: 'start'
|
385
|
+
},
|
386
|
+
{
|
387
|
+
id: 'BoxOffice',
|
388
|
+
text: 'Box Office',
|
389
|
+
width: '20%',
|
390
|
+
textAlign: 'end'
|
391
|
+
}
|
392
|
+
]}
|
393
|
+
rows={[
|
394
|
+
{
|
395
|
+
id: '1',
|
396
|
+
Title: 'The Shawshank Redemption',
|
397
|
+
Year: 1994,
|
398
|
+
Summary:
|
399
|
+
'Two imprisoned men bond over a number of years, finding solace and eventual redemption through acts of common decency.',
|
400
|
+
BoxOffice: '$28,341,469'
|
401
|
+
},
|
402
|
+
{
|
403
|
+
id: '2',
|
404
|
+
Title: 'The Godfather',
|
405
|
+
Year: 1972,
|
406
|
+
Summary:
|
407
|
+
'The aging patriarch of an organized crime dynasty transfers control of his clandestine empire to his reluctant son.',
|
408
|
+
BoxOffice: '$133,698,921'
|
409
|
+
},
|
410
|
+
{
|
411
|
+
id: '3',
|
412
|
+
Title: 'The Godfather: Part II',
|
413
|
+
Year: 1974,
|
414
|
+
Summary:
|
415
|
+
'The early life and career of Vito Corleone in 1920s New York City is portrayed, while his son, Michael, expands and tightens his grip on the family crime syndicate.',
|
416
|
+
BoxOffice: '$47,542,841'
|
417
|
+
}
|
418
|
+
]}
|
419
|
+
/>
|
420
|
+
)
|
421
|
+
```
|
238
422
|
|
239
423
|
### A sortable table using our Responsive component
|
240
424
|
|
@@ -242,56 +426,293 @@ Resize the window to see how column headers transition into a `Select` for sorti
|
|
242
426
|
|
243
427
|
By default, the options in the `Select` for sorting in stacked layout are generated from the `id` property of the `Table.ColHeader` components. If you want to display custom strings, use the `stackedSortByLabel` property.
|
244
428
|
|
245
|
-
```javascript
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
429
|
+
- ```javascript
|
430
|
+
class SortableTable extends React.Component {
|
431
|
+
constructor(props) {
|
432
|
+
super(props)
|
433
|
+
const { headers } = props
|
434
|
+
|
435
|
+
const initialColWidth = {}
|
436
|
+
headers.forEach((header) => {
|
437
|
+
initialColWidth[header.id] = 'start'
|
438
|
+
})
|
439
|
+
|
440
|
+
this.state = {
|
441
|
+
sortBy: headers && headers[0] && headers[0].id,
|
442
|
+
ascending: true,
|
443
|
+
colTextAligns: initialColWidth
|
444
|
+
}
|
445
|
+
}
|
446
|
+
|
447
|
+
handleSort = (event, { id }) => {
|
448
|
+
const { sortBy, ascending } = this.state
|
449
|
+
|
450
|
+
if (id === sortBy) {
|
451
|
+
this.setState({
|
452
|
+
ascending: !ascending
|
453
|
+
})
|
454
|
+
} else {
|
455
|
+
this.setState({
|
456
|
+
sortBy: id,
|
457
|
+
ascending: true
|
458
|
+
})
|
459
|
+
}
|
460
|
+
}
|
461
|
+
|
462
|
+
handleColTextAlignChange(id, value) {
|
463
|
+
this.setState((state) => ({
|
464
|
+
colTextAligns: {
|
465
|
+
...state.colTextAligns,
|
466
|
+
[id]: value
|
467
|
+
}
|
468
|
+
}))
|
469
|
+
}
|
470
|
+
|
471
|
+
renderHeaderRow(direction) {
|
472
|
+
const { headers } = this.props
|
473
|
+
const { colTextAligns, sortBy } = this.state
|
474
|
+
|
475
|
+
return (
|
476
|
+
<Table.Row>
|
477
|
+
{(headers || []).map(({ id, text, width }) => (
|
478
|
+
<Table.ColHeader
|
479
|
+
key={id}
|
480
|
+
id={id}
|
481
|
+
width={width}
|
482
|
+
{...(direction && {
|
483
|
+
textAlign: colTextAligns[id],
|
484
|
+
stackedSortByLabel: text,
|
485
|
+
onRequestSort: this.handleSort,
|
486
|
+
sortDirection: id === sortBy ? direction : 'none'
|
487
|
+
})}
|
488
|
+
>
|
489
|
+
{text}
|
490
|
+
</Table.ColHeader>
|
491
|
+
))}
|
492
|
+
</Table.Row>
|
493
|
+
)
|
494
|
+
}
|
253
495
|
|
496
|
+
renderOptions() {
|
497
|
+
const { headers } = this.props
|
498
|
+
const { colTextAligns } = this.state
|
499
|
+
|
500
|
+
return (
|
501
|
+
<ToggleGroup
|
502
|
+
size="small"
|
503
|
+
toggleLabel="Set text-align for columns"
|
504
|
+
summary="Set text-align for columns"
|
505
|
+
background="default"
|
506
|
+
>
|
507
|
+
<Table caption="Set text-align for columns">
|
508
|
+
<Table.Head>{this.renderHeaderRow()}</Table.Head>
|
509
|
+
<Table.Body>
|
510
|
+
<Table.Row>
|
511
|
+
{Object.entries(colTextAligns).map(([headerId, textAlign]) => {
|
512
|
+
return (
|
513
|
+
<Table.Cell key={headerId}>
|
514
|
+
<RadioInputGroup
|
515
|
+
description={
|
516
|
+
<ScreenReaderContent>
|
517
|
+
Set text-align for column: {headerId}
|
518
|
+
</ScreenReaderContent>
|
519
|
+
}
|
520
|
+
name={`columnTextAlign_${headerId}`}
|
521
|
+
value={textAlign}
|
522
|
+
margin="0 0 small"
|
523
|
+
size="small"
|
524
|
+
onChange={(e, value) =>
|
525
|
+
this.handleColTextAlignChange(headerId, value)
|
526
|
+
}
|
527
|
+
>
|
528
|
+
<RadioInput label="start" value="start" />
|
529
|
+
<RadioInput label="center" value="center" />
|
530
|
+
<RadioInput label="end" value="end" />
|
531
|
+
</RadioInputGroup>
|
532
|
+
</Table.Cell>
|
533
|
+
)
|
534
|
+
})}
|
535
|
+
</Table.Row>
|
536
|
+
</Table.Body>
|
537
|
+
</Table>
|
538
|
+
</ToggleGroup>
|
539
|
+
)
|
540
|
+
}
|
541
|
+
|
542
|
+
render() {
|
543
|
+
const { caption, headers, rows } = this.props
|
544
|
+
const { sortBy, ascending, colTextAligns } = this.state
|
545
|
+
const direction = ascending ? 'ascending' : 'descending'
|
546
|
+
const sortedRows = [...(rows || [])].sort((a, b) => {
|
547
|
+
if (a[sortBy] < b[sortBy]) {
|
548
|
+
return -1
|
549
|
+
}
|
550
|
+
if (a[sortBy] > b[sortBy]) {
|
551
|
+
return 1
|
552
|
+
}
|
553
|
+
return 0
|
554
|
+
})
|
555
|
+
|
556
|
+
if (!ascending) {
|
557
|
+
sortedRows.reverse()
|
558
|
+
}
|
559
|
+
return (
|
560
|
+
<Responsive
|
561
|
+
query={{
|
562
|
+
small: { maxWidth: '40rem' },
|
563
|
+
large: { minWidth: '41rem' }
|
564
|
+
}}
|
565
|
+
props={{
|
566
|
+
small: { layout: 'stacked' },
|
567
|
+
large: { layout: 'auto' }
|
568
|
+
}}
|
569
|
+
>
|
570
|
+
{(props) => (
|
571
|
+
<div>
|
572
|
+
{props.layout !== 'stacked' && (
|
573
|
+
<View display="block" margin="0 0 medium">
|
574
|
+
{this.renderOptions()}
|
575
|
+
</View>
|
576
|
+
)}
|
577
|
+
|
578
|
+
<Table
|
579
|
+
caption={`${caption}: sorted by ${sortBy} in ${direction} order`}
|
580
|
+
{...props}
|
581
|
+
>
|
582
|
+
<Table.Head renderSortLabel="Sort by">
|
583
|
+
{this.renderHeaderRow(direction)}
|
584
|
+
</Table.Head>
|
585
|
+
<Table.Body>
|
586
|
+
{sortedRows.map((row) => (
|
587
|
+
<Table.Row key={row.id}>
|
588
|
+
{headers.map(({ id, renderCell }) => (
|
589
|
+
<Table.Cell key={id} textAlign={colTextAligns[id]}>
|
590
|
+
{renderCell ? renderCell(row[id]) : row[id]}
|
591
|
+
</Table.Cell>
|
592
|
+
))}
|
593
|
+
</Table.Row>
|
594
|
+
))}
|
595
|
+
</Table.Body>
|
596
|
+
</Table>
|
597
|
+
<Alert
|
598
|
+
liveRegion={() => document.getElementById('flash-messages')}
|
599
|
+
liveRegionPoliteness="polite"
|
600
|
+
screenReaderOnly
|
601
|
+
>
|
602
|
+
{`Sorted by ${sortBy} in ${direction} order`}
|
603
|
+
</Alert>
|
604
|
+
</div>
|
605
|
+
)}
|
606
|
+
</Responsive>
|
607
|
+
)
|
608
|
+
}
|
609
|
+
}
|
610
|
+
|
611
|
+
render(
|
612
|
+
<SortableTable
|
613
|
+
caption="Top rated movies"
|
614
|
+
headers={[
|
615
|
+
{
|
616
|
+
id: 'rank',
|
617
|
+
text: 'Rank',
|
618
|
+
width: '15%'
|
619
|
+
},
|
620
|
+
{
|
621
|
+
id: 'title',
|
622
|
+
text: 'Title',
|
623
|
+
width: '55%'
|
624
|
+
},
|
625
|
+
{
|
626
|
+
id: 'year',
|
627
|
+
text: 'Year',
|
628
|
+
width: '15%'
|
629
|
+
},
|
630
|
+
{
|
631
|
+
id: 'rating',
|
632
|
+
text: 'Rating',
|
633
|
+
width: '15%',
|
634
|
+
renderCell: (rating) => rating.toFixed(1)
|
635
|
+
}
|
636
|
+
]}
|
637
|
+
rows={[
|
638
|
+
{
|
639
|
+
id: '1',
|
640
|
+
rank: 1,
|
641
|
+
title: 'The Shawshank Redemption',
|
642
|
+
year: 1994,
|
643
|
+
rating: 9.3
|
644
|
+
},
|
645
|
+
{
|
646
|
+
id: '2',
|
647
|
+
rank: 2,
|
648
|
+
title: 'The Godfather',
|
649
|
+
year: 1972,
|
650
|
+
rating: 9.2
|
651
|
+
},
|
652
|
+
{
|
653
|
+
id: '3',
|
654
|
+
rank: 3,
|
655
|
+
title: 'The Godfather: Part II',
|
656
|
+
year: 1974,
|
657
|
+
rating: 9.0
|
658
|
+
},
|
659
|
+
{
|
660
|
+
id: '4',
|
661
|
+
rank: 4,
|
662
|
+
title: 'The Dark Knight',
|
663
|
+
year: 2008,
|
664
|
+
rating: 9.0
|
665
|
+
},
|
666
|
+
{
|
667
|
+
id: '5',
|
668
|
+
rank: 5,
|
669
|
+
title: '12 Angry Men',
|
670
|
+
year: 1957,
|
671
|
+
rating: 8.9
|
672
|
+
}
|
673
|
+
]}
|
674
|
+
/>
|
675
|
+
)
|
676
|
+
```
|
677
|
+
|
678
|
+
- ```javascript
|
679
|
+
const SortableTable = ({ caption, headers, rows }) => {
|
254
680
|
const initialColWidth = {}
|
255
681
|
headers.forEach((header) => {
|
256
682
|
initialColWidth[header.id] = 'start'
|
257
683
|
})
|
258
684
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
colTextAligns: initialColWidth
|
263
|
-
}
|
264
|
-
}
|
685
|
+
const [sortBy, setSortBy] = useState(headers && headers[0] && headers[0].id)
|
686
|
+
const [ascending, setAscending] = useState(true)
|
687
|
+
const [colTextAligns, setColTextAligns] = useState(initialColWidth)
|
265
688
|
|
266
|
-
|
267
|
-
|
689
|
+
const sortedRows = useMemo(() => {
|
690
|
+
if (!sortBy) return rows
|
268
691
|
|
269
|
-
|
270
|
-
|
271
|
-
ascending: !ascending,
|
272
|
-
})
|
273
|
-
} else {
|
274
|
-
this.setState({
|
275
|
-
sortBy: id,
|
276
|
-
ascending: true,
|
692
|
+
const sorted = [...rows].sort((a, b) => {
|
693
|
+
return a[sortBy] > b[sortBy] ? 1 : a[sortBy] < b[sortBy] ? -1 : 0
|
277
694
|
})
|
278
|
-
}
|
279
|
-
}
|
280
695
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
696
|
+
return ascending ? sorted : sorted.reverse()
|
697
|
+
}, [sortBy, ascending, rows])
|
698
|
+
|
699
|
+
const handleSort = (event, { id }) => {
|
700
|
+
if (id === sortBy) {
|
701
|
+
setAscending(!ascending)
|
702
|
+
} else {
|
703
|
+
setSortBy(id)
|
704
|
+
setAscending(true)
|
286
705
|
}
|
287
|
-
}
|
288
|
-
}
|
706
|
+
}
|
289
707
|
|
290
|
-
|
291
|
-
|
292
|
-
|
708
|
+
const handleColTextAlignChange = (id, value) => {
|
709
|
+
setColTextAligns((prevState) => ({
|
710
|
+
...prevState,
|
711
|
+
[id]: value
|
712
|
+
}))
|
713
|
+
}
|
293
714
|
|
294
|
-
|
715
|
+
const renderHeaderRow = (direction) => (
|
295
716
|
<Table.Row>
|
296
717
|
{(headers || []).map(({ id, text, width }) => (
|
297
718
|
<Table.ColHeader
|
@@ -301,7 +722,7 @@ class SortableTable extends React.Component {
|
|
301
722
|
{...(direction && {
|
302
723
|
textAlign: colTextAligns[id],
|
303
724
|
stackedSortByLabel: text,
|
304
|
-
onRequestSort:
|
725
|
+
onRequestSort: handleSort,
|
305
726
|
sortDirection: id === sortBy ? direction : 'none'
|
306
727
|
})}
|
307
728
|
>
|
@@ -310,126 +731,64 @@ class SortableTable extends React.Component {
|
|
310
731
|
))}
|
311
732
|
</Table.Row>
|
312
733
|
)
|
313
|
-
}
|
314
|
-
|
315
|
-
renderOptions () {
|
316
|
-
const { headers } = this.props
|
317
|
-
const { colTextAligns } = this.state
|
318
734
|
|
319
|
-
|
735
|
+
const renderOptions = () => (
|
320
736
|
<ToggleGroup
|
321
737
|
size="small"
|
322
738
|
toggleLabel="Set text-align for columns"
|
323
739
|
summary="Set text-align for columns"
|
324
740
|
background="default"
|
325
741
|
>
|
326
|
-
<Table caption=
|
327
|
-
<Table.Head>
|
328
|
-
{this.renderHeaderRow()}
|
329
|
-
</Table.Head>
|
742
|
+
<Table caption="Set text-align for columns">
|
743
|
+
<Table.Head>{renderHeaderRow()}</Table.Head>
|
330
744
|
<Table.Body>
|
331
745
|
<Table.Row>
|
332
|
-
{Object.entries(colTextAligns).map(([headerId, textAlign]) =>
|
333
|
-
|
334
|
-
<
|
335
|
-
|
336
|
-
|
746
|
+
{Object.entries(colTextAligns).map(([headerId, textAlign]) => (
|
747
|
+
<Table.Cell key={headerId}>
|
748
|
+
<RadioInputGroup
|
749
|
+
description={
|
750
|
+
<ScreenReaderContent>
|
751
|
+
Set text-align for column: {headerId}
|
752
|
+
</ScreenReaderContent>
|
753
|
+
}
|
754
|
+
name={`columnTextAlign_${headerId}`}
|
755
|
+
value={textAlign}
|
756
|
+
margin="0 0 small"
|
757
|
+
size="small"
|
758
|
+
onChange={(e, value) =>
|
759
|
+
handleColTextAlignChange(headerId, value)
|
760
|
+
}
|
337
761
|
>
|
338
|
-
<
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
name={`columnTextAlign_${headerId}`}
|
345
|
-
value={textAlign}
|
346
|
-
margin="0 0 small"
|
347
|
-
size="small"
|
348
|
-
onChange={
|
349
|
-
(e, value) => this.handleColTextAlignChange(headerId, value)
|
350
|
-
}
|
351
|
-
>
|
352
|
-
<RadioInput label="start" value="start" />
|
353
|
-
<RadioInput label="center" value="center" />
|
354
|
-
<RadioInput label="end" value="end" />
|
355
|
-
</RadioInputGroup>
|
356
|
-
</Table.Cell>
|
357
|
-
)
|
358
|
-
})}
|
762
|
+
<RadioInput label="start" value="start" />
|
763
|
+
<RadioInput label="center" value="center" />
|
764
|
+
<RadioInput label="end" value="end" />
|
765
|
+
</RadioInputGroup>
|
766
|
+
</Table.Cell>
|
767
|
+
))}
|
359
768
|
</Table.Row>
|
360
769
|
</Table.Body>
|
361
770
|
</Table>
|
362
771
|
</ToggleGroup>
|
363
772
|
)
|
364
773
|
|
365
|
-
return (
|
366
|
-
<FormField id="columnTextAlign" label="Set column text-align">
|
367
|
-
<Flex margin="0 0 medium">
|
368
|
-
{Object.entries(colTextAligns).map(([headerId, textAlign]) => {
|
369
|
-
return (
|
370
|
-
<Flex.Item
|
371
|
-
key={headerId}
|
372
|
-
width={headers.find(header => header.id === headerId).width}
|
373
|
-
>
|
374
|
-
<RadioInputGroup
|
375
|
-
description={
|
376
|
-
<ScreenReaderContent>
|
377
|
-
Column {headerId}textAlign
|
378
|
-
</ScreenReaderContent>
|
379
|
-
}
|
380
|
-
name={`Column "${headerId}" textAlign`}
|
381
|
-
value={textAlign}
|
382
|
-
margin="0 0 small"
|
383
|
-
size="small"
|
384
|
-
onChange={
|
385
|
-
(e, value) => this.handleColTextAlignChange(headerId, value)
|
386
|
-
}
|
387
|
-
>
|
388
|
-
<RadioInput label="start" value="start" />
|
389
|
-
<RadioInput label="center" value="center" />
|
390
|
-
<RadioInput label="end" value="end" />
|
391
|
-
</RadioInputGroup>
|
392
|
-
</Flex.Item>
|
393
|
-
)
|
394
|
-
})}
|
395
|
-
</Flex>
|
396
|
-
</FormField>
|
397
|
-
)
|
398
|
-
}
|
399
|
-
|
400
|
-
render() {
|
401
|
-
const { caption, headers, rows } = this.props
|
402
|
-
const { sortBy, ascending, colTextAligns } = this.state
|
403
774
|
const direction = ascending ? 'ascending' : 'descending'
|
404
|
-
const sortedRows = [...(rows || [])].sort((a, b) => {
|
405
|
-
if (a[sortBy] < b[sortBy]) {
|
406
|
-
return -1
|
407
|
-
}
|
408
|
-
if (a[sortBy] > b[sortBy]) {
|
409
|
-
return 1
|
410
|
-
}
|
411
|
-
return 0
|
412
|
-
})
|
413
775
|
|
414
|
-
if (!ascending) {
|
415
|
-
sortedRows.reverse()
|
416
|
-
}
|
417
776
|
return (
|
418
777
|
<Responsive
|
419
778
|
query={{
|
420
779
|
small: { maxWidth: '40rem' },
|
421
|
-
large: { minWidth: '41rem' }
|
780
|
+
large: { minWidth: '41rem' }
|
422
781
|
}}
|
423
782
|
props={{
|
424
783
|
small: { layout: 'stacked' },
|
425
|
-
large: { layout: 'auto' }
|
784
|
+
large: { layout: 'auto' }
|
426
785
|
}}
|
427
786
|
>
|
428
787
|
{(props) => (
|
429
788
|
<div>
|
430
789
|
{props.layout !== 'stacked' && (
|
431
790
|
<View display="block" margin="0 0 medium">
|
432
|
-
{
|
791
|
+
{renderOptions()}
|
433
792
|
</View>
|
434
793
|
)}
|
435
794
|
|
@@ -438,7 +797,7 @@ class SortableTable extends React.Component {
|
|
438
797
|
{...props}
|
439
798
|
>
|
440
799
|
<Table.Head renderSortLabel="Sort by">
|
441
|
-
{
|
800
|
+
{renderHeaderRow(direction)}
|
442
801
|
</Table.Head>
|
443
802
|
<Table.Body>
|
444
803
|
{sortedRows.map((row) => (
|
@@ -464,118 +823,446 @@ class SortableTable extends React.Component {
|
|
464
823
|
</Responsive>
|
465
824
|
)
|
466
825
|
}
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
}
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
}
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
```
|
826
|
+
|
827
|
+
render(
|
828
|
+
<SortableTable
|
829
|
+
caption="Top rated movies"
|
830
|
+
headers={[
|
831
|
+
{
|
832
|
+
id: 'rank',
|
833
|
+
text: 'Rank',
|
834
|
+
width: '15%'
|
835
|
+
},
|
836
|
+
{
|
837
|
+
id: 'title',
|
838
|
+
text: 'Title',
|
839
|
+
width: '55%'
|
840
|
+
},
|
841
|
+
{
|
842
|
+
id: 'year',
|
843
|
+
text: 'Year',
|
844
|
+
width: '15%'
|
845
|
+
},
|
846
|
+
{
|
847
|
+
id: 'rating',
|
848
|
+
text: 'Rating',
|
849
|
+
width: '15%',
|
850
|
+
renderCell: (rating) => rating.toFixed(1)
|
851
|
+
}
|
852
|
+
]}
|
853
|
+
rows={[
|
854
|
+
{
|
855
|
+
id: '1',
|
856
|
+
rank: 1,
|
857
|
+
title: 'The Shawshank Redemption',
|
858
|
+
year: 1994,
|
859
|
+
rating: 9.3
|
860
|
+
},
|
861
|
+
{
|
862
|
+
id: '2',
|
863
|
+
rank: 2,
|
864
|
+
title: 'The Godfather',
|
865
|
+
year: 1972,
|
866
|
+
rating: 9.2
|
867
|
+
},
|
868
|
+
{
|
869
|
+
id: '3',
|
870
|
+
rank: 3,
|
871
|
+
title: 'The Godfather: Part II',
|
872
|
+
year: 1974,
|
873
|
+
rating: 9.0
|
874
|
+
},
|
875
|
+
{
|
876
|
+
id: '4',
|
877
|
+
rank: 4,
|
878
|
+
title: 'The Dark Knight',
|
879
|
+
year: 2008,
|
880
|
+
rating: 9.0
|
881
|
+
},
|
882
|
+
{
|
883
|
+
id: '5',
|
884
|
+
rank: 5,
|
885
|
+
title: '12 Angry Men',
|
886
|
+
year: 1957,
|
887
|
+
rating: 8.9
|
888
|
+
}
|
889
|
+
]}
|
890
|
+
/>
|
891
|
+
)
|
892
|
+
```
|
535
893
|
|
536
894
|
### A sortable table with selection and pagination
|
537
895
|
|
538
896
|
The composition order is important. `SelectableTable` -> `PaginatedTable` -> `SortableTable`, so
|
539
897
|
that selection does not re-paginate or re-sort the table, and pagination does not re-sort the table.
|
540
898
|
|
541
|
-
```javascript
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
899
|
+
- ```javascript
|
900
|
+
class SelectableTable extends React.Component {
|
901
|
+
constructor(props) {
|
902
|
+
super(props)
|
903
|
+
this.state = {
|
904
|
+
selected: new Set()
|
905
|
+
}
|
906
|
+
}
|
907
|
+
|
908
|
+
handleSelectAll = (allSelected) => {
|
909
|
+
const { rowIds } = this.props
|
910
|
+
|
911
|
+
this.setState({
|
912
|
+
selected: allSelected ? new Set() : new Set(rowIds)
|
913
|
+
})
|
914
|
+
}
|
915
|
+
|
916
|
+
handleSelectRow = (rowSelected, rowId) => {
|
917
|
+
const { selected } = this.state
|
918
|
+
const copy = new Set(selected)
|
919
|
+
if (rowSelected) {
|
920
|
+
copy.delete(rowId)
|
921
|
+
} else {
|
922
|
+
copy.add(rowId)
|
923
|
+
}
|
924
|
+
|
925
|
+
this.setState({
|
926
|
+
selected: copy
|
927
|
+
})
|
928
|
+
}
|
929
|
+
|
930
|
+
render() {
|
931
|
+
const { caption, headers, rows, onSort, sortBy, ascending, rowIds } =
|
932
|
+
this.props
|
933
|
+
const { selected } = this.state
|
934
|
+
const allSelected =
|
935
|
+
selected.size > 0 && rowIds.every((id) => selected.has(id))
|
936
|
+
const someSelected = selected.size > 0 && !allSelected
|
937
|
+
const direction = ascending ? 'ascending' : 'descending'
|
938
|
+
|
939
|
+
return (
|
940
|
+
<Responsive
|
941
|
+
query={{
|
942
|
+
small: { maxWidth: '40rem' },
|
943
|
+
large: { minWidth: '41rem' }
|
944
|
+
}}
|
945
|
+
props={{
|
946
|
+
small: { layout: 'stacked' },
|
947
|
+
large: { layout: 'auto' }
|
948
|
+
}}
|
949
|
+
>
|
950
|
+
{(props) => (
|
951
|
+
<div>
|
952
|
+
<View as="div" padding="small" background="primary-inverse">
|
953
|
+
{`${selected.size} of ${rowIds.length} selected`}
|
954
|
+
</View>
|
955
|
+
<Table
|
956
|
+
caption={`${caption}: sorted by ${sortBy} in ${direction} order`}
|
957
|
+
{...props}
|
958
|
+
>
|
959
|
+
<Table.Head
|
960
|
+
renderSortLabel={
|
961
|
+
<ScreenReaderContent>Sort by</ScreenReaderContent>
|
962
|
+
}
|
963
|
+
>
|
964
|
+
<Table.Row>
|
965
|
+
<Table.ColHeader id="select">
|
966
|
+
<Checkbox
|
967
|
+
label={
|
968
|
+
<ScreenReaderContent>Select all</ScreenReaderContent>
|
969
|
+
}
|
970
|
+
onChange={() => this.handleSelectAll(allSelected)}
|
971
|
+
checked={allSelected}
|
972
|
+
indeterminate={someSelected}
|
973
|
+
/>
|
974
|
+
</Table.ColHeader>
|
975
|
+
{(headers || []).map(({ id, text, width }) => (
|
976
|
+
<Table.ColHeader
|
977
|
+
key={id}
|
978
|
+
id={id}
|
979
|
+
width={width}
|
980
|
+
onRequestSort={onSort}
|
981
|
+
sortDirection={id === sortBy ? direction : 'none'}
|
982
|
+
>
|
983
|
+
{text}
|
984
|
+
</Table.ColHeader>
|
985
|
+
))}
|
986
|
+
</Table.Row>
|
987
|
+
</Table.Head>
|
988
|
+
<Table.Body>
|
989
|
+
{(rows || []).map((row) => {
|
990
|
+
const rowSelected = selected.has(row.id)
|
991
|
+
|
992
|
+
return (
|
993
|
+
<Table.Row key={row.id}>
|
994
|
+
<Table.RowHeader>
|
995
|
+
<Checkbox
|
996
|
+
label={
|
997
|
+
<ScreenReaderContent>
|
998
|
+
Select row
|
999
|
+
</ScreenReaderContent>
|
1000
|
+
}
|
1001
|
+
onChange={() =>
|
1002
|
+
this.handleSelectRow(rowSelected, row.id)
|
1003
|
+
}
|
1004
|
+
checked={rowSelected}
|
1005
|
+
/>
|
1006
|
+
</Table.RowHeader>
|
1007
|
+
{(headers || []).map(({ id, renderCell }) => (
|
1008
|
+
<Table.Cell key={id}>
|
1009
|
+
{renderCell ? renderCell(row[id]) : row[id]}
|
1010
|
+
</Table.Cell>
|
1011
|
+
))}
|
1012
|
+
</Table.Row>
|
1013
|
+
)
|
1014
|
+
})}
|
1015
|
+
</Table.Body>
|
1016
|
+
</Table>
|
1017
|
+
<Alert
|
1018
|
+
liveRegion={() => document.getElementById('flash-messages')}
|
1019
|
+
liveRegionPoliteness="polite"
|
1020
|
+
screenReaderOnly
|
1021
|
+
>
|
1022
|
+
{`${selected.size} of ${rowIds.length} selected`}
|
1023
|
+
</Alert>
|
1024
|
+
</div>
|
1025
|
+
)}
|
1026
|
+
</Responsive>
|
1027
|
+
)
|
550
1028
|
}
|
551
1029
|
}
|
552
1030
|
|
553
|
-
|
554
|
-
|
1031
|
+
class PaginatedTable extends React.Component {
|
1032
|
+
constructor(props) {
|
1033
|
+
super(props)
|
1034
|
+
this.state = {
|
1035
|
+
page: 0
|
1036
|
+
}
|
1037
|
+
}
|
555
1038
|
|
556
|
-
|
557
|
-
|
558
|
-
|
1039
|
+
handleClick = (page) => {
|
1040
|
+
this.setState({
|
1041
|
+
page
|
1042
|
+
})
|
1043
|
+
}
|
1044
|
+
|
1045
|
+
handleSort = (event, options) => {
|
1046
|
+
const { onSort } = this.props
|
1047
|
+
|
1048
|
+
this.setState({
|
1049
|
+
page: 0
|
1050
|
+
})
|
1051
|
+
onSort(event, options)
|
1052
|
+
}
|
1053
|
+
|
1054
|
+
render() {
|
1055
|
+
const { caption, headers, rows, sortBy, ascending, perPage } = this.props
|
1056
|
+
const { page } = this.state
|
1057
|
+
const startIndex = page * perPage
|
1058
|
+
const slicedRows = rows.slice(startIndex, startIndex + perPage)
|
1059
|
+
const pageCount = perPage && Math.ceil(rows.length / perPage)
|
1060
|
+
|
1061
|
+
return (
|
1062
|
+
<div>
|
1063
|
+
<SelectableTable
|
1064
|
+
caption={caption}
|
1065
|
+
headers={headers}
|
1066
|
+
rows={slicedRows}
|
1067
|
+
onSort={this.handleSort}
|
1068
|
+
sortBy={sortBy}
|
1069
|
+
ascending={ascending}
|
1070
|
+
rowIds={rows.map((row) => row.id)}
|
1071
|
+
/>
|
1072
|
+
{pageCount > 1 && (
|
1073
|
+
<Pagination
|
1074
|
+
variant="compact"
|
1075
|
+
labelNext="Next Page"
|
1076
|
+
labelPrev="Previous Page"
|
1077
|
+
margin="large"
|
1078
|
+
>
|
1079
|
+
{Array.from(Array(pageCount), (item, index) => (
|
1080
|
+
<Pagination.Page
|
1081
|
+
key={index}
|
1082
|
+
onClick={() => this.handleClick(index)}
|
1083
|
+
current={index === page}
|
1084
|
+
>
|
1085
|
+
{index + 1}
|
1086
|
+
</Pagination.Page>
|
1087
|
+
))}
|
1088
|
+
</Pagination>
|
1089
|
+
)}
|
1090
|
+
<Alert
|
1091
|
+
liveRegion={() => document.getElementById('flash-messages')}
|
1092
|
+
liveRegionPoliteness="polite"
|
1093
|
+
screenReaderOnly
|
1094
|
+
>
|
1095
|
+
{`Table page ${page + 1} of ${pageCount}`}
|
1096
|
+
</Alert>
|
1097
|
+
</div>
|
1098
|
+
)
|
1099
|
+
}
|
559
1100
|
}
|
560
1101
|
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
1102
|
+
class SortableTable extends React.Component {
|
1103
|
+
constructor(props) {
|
1104
|
+
super(props)
|
1105
|
+
const { headers } = props
|
1106
|
+
|
1107
|
+
this.state = {
|
1108
|
+
sortBy: headers && headers[0] && headers[0].id,
|
1109
|
+
ascending: true
|
1110
|
+
}
|
568
1111
|
}
|
569
1112
|
|
570
|
-
|
571
|
-
|
572
|
-
|
1113
|
+
handleSort = (event, { id }) => {
|
1114
|
+
const { sortBy, ascending } = this.state
|
1115
|
+
|
1116
|
+
if (id === sortBy) {
|
1117
|
+
this.setState({
|
1118
|
+
ascending: !ascending
|
1119
|
+
})
|
1120
|
+
} else {
|
1121
|
+
this.setState({
|
1122
|
+
sortBy: id,
|
1123
|
+
ascending: true
|
1124
|
+
})
|
1125
|
+
}
|
1126
|
+
}
|
1127
|
+
|
1128
|
+
render() {
|
1129
|
+
const { caption, headers, rows, perPage } = this.props
|
1130
|
+
const { sortBy, ascending } = this.state
|
1131
|
+
const sortedRows = [...rows].sort((a, b) => {
|
1132
|
+
if (a[sortBy] < b[sortBy]) {
|
1133
|
+
return -1
|
1134
|
+
}
|
1135
|
+
if (a[sortBy] > b[sortBy]) {
|
1136
|
+
return 1
|
1137
|
+
}
|
1138
|
+
return 0
|
1139
|
+
})
|
1140
|
+
|
1141
|
+
if (!ascending) {
|
1142
|
+
sortedRows.reverse()
|
1143
|
+
}
|
1144
|
+
return (
|
1145
|
+
<div>
|
1146
|
+
<PaginatedTable
|
1147
|
+
caption={caption}
|
1148
|
+
headers={headers}
|
1149
|
+
rows={sortedRows}
|
1150
|
+
onSort={this.handleSort}
|
1151
|
+
sortBy={sortBy}
|
1152
|
+
ascending={ascending}
|
1153
|
+
perPage={perPage}
|
1154
|
+
/>
|
1155
|
+
<Alert
|
1156
|
+
liveRegion={() => document.getElementById('flash-messages')}
|
1157
|
+
liveRegionPoliteness="polite"
|
1158
|
+
screenReaderOnly
|
1159
|
+
>
|
1160
|
+
{`Sorted by ${sortBy} in ${
|
1161
|
+
ascending ? 'ascending' : 'descending'
|
1162
|
+
} order`}
|
1163
|
+
</Alert>
|
1164
|
+
</div>
|
1165
|
+
)
|
1166
|
+
}
|
573
1167
|
}
|
574
1168
|
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
1169
|
+
const renderRating = (rating) => (
|
1170
|
+
<Rating label="Rating" valueNow={rating} valueMax={10} iconCount={5} />
|
1171
|
+
)
|
1172
|
+
|
1173
|
+
render(
|
1174
|
+
<SortableTable
|
1175
|
+
caption="Top rated movies"
|
1176
|
+
headers={[
|
1177
|
+
{
|
1178
|
+
id: 'Rank',
|
1179
|
+
text: 'Rank'
|
1180
|
+
},
|
1181
|
+
{
|
1182
|
+
id: 'Title',
|
1183
|
+
text: 'Title',
|
1184
|
+
width: '40%'
|
1185
|
+
},
|
1186
|
+
{
|
1187
|
+
id: 'Year',
|
1188
|
+
text: 'Year'
|
1189
|
+
},
|
1190
|
+
{
|
1191
|
+
id: 'Rating',
|
1192
|
+
text: 'Rating',
|
1193
|
+
renderCell: renderRating
|
1194
|
+
}
|
1195
|
+
]}
|
1196
|
+
rows={[
|
1197
|
+
{
|
1198
|
+
id: '1',
|
1199
|
+
Rank: 1,
|
1200
|
+
Title: 'The Shawshank Redemption',
|
1201
|
+
Year: 1994,
|
1202
|
+
Rating: 9.3
|
1203
|
+
},
|
1204
|
+
{
|
1205
|
+
id: '2',
|
1206
|
+
Rank: 2,
|
1207
|
+
Title: 'The Godfather',
|
1208
|
+
Year: 1972,
|
1209
|
+
Rating: 9.2
|
1210
|
+
},
|
1211
|
+
{
|
1212
|
+
id: '3',
|
1213
|
+
Rank: 3,
|
1214
|
+
Title: 'The Godfather: Part II',
|
1215
|
+
Year: 1974,
|
1216
|
+
Rating: 9.0
|
1217
|
+
},
|
1218
|
+
{
|
1219
|
+
id: '4',
|
1220
|
+
Rank: 4,
|
1221
|
+
Title: 'The Dark Knight',
|
1222
|
+
Year: 2008,
|
1223
|
+
Rating: 9.0
|
1224
|
+
},
|
1225
|
+
{
|
1226
|
+
id: '5',
|
1227
|
+
Rank: 5,
|
1228
|
+
Title: '12 Angry Men',
|
1229
|
+
Year: 1957,
|
1230
|
+
Rating: 8.9
|
1231
|
+
}
|
1232
|
+
]}
|
1233
|
+
perPage={3}
|
1234
|
+
/>
|
1235
|
+
)
|
1236
|
+
```
|
1237
|
+
|
1238
|
+
- ```javascript
|
1239
|
+
const SelectableTable = ({
|
1240
|
+
caption,
|
1241
|
+
headers,
|
1242
|
+
rows,
|
1243
|
+
onSort,
|
1244
|
+
sortBy,
|
1245
|
+
ascending,
|
1246
|
+
rowIds
|
1247
|
+
}) => {
|
1248
|
+
const [selected, setSelected] = useState(new Set())
|
1249
|
+
|
1250
|
+
const handleSelectAll = (allSelected) => {
|
1251
|
+
setSelected(allSelected ? new Set() : new Set(rowIds))
|
1252
|
+
}
|
1253
|
+
|
1254
|
+
const handleSelectRow = (rowSelected, rowId) => {
|
1255
|
+
const copy = new Set(selected)
|
1256
|
+
if (rowSelected) {
|
1257
|
+
copy.delete(rowId)
|
1258
|
+
} else {
|
1259
|
+
copy.add(rowId)
|
1260
|
+
}
|
1261
|
+
setSelected(copy)
|
1262
|
+
}
|
1263
|
+
|
1264
|
+
const allSelected =
|
1265
|
+
selected.size > 0 && rowIds.every((id) => selected.has(id))
|
579
1266
|
const someSelected = selected.size > 0 && !allSelected
|
580
1267
|
const direction = ascending ? 'ascending' : 'descending'
|
581
1268
|
|
@@ -583,47 +1270,49 @@ class SelectableTable extends React.Component {
|
|
583
1270
|
<Responsive
|
584
1271
|
query={{
|
585
1272
|
small: { maxWidth: '40rem' },
|
586
|
-
large: { minWidth: '41rem' }
|
1273
|
+
large: { minWidth: '41rem' }
|
587
1274
|
}}
|
588
1275
|
props={{
|
589
1276
|
small: { layout: 'stacked' },
|
590
|
-
large: { layout: 'auto' }
|
1277
|
+
large: { layout: 'auto' }
|
591
1278
|
}}
|
592
1279
|
>
|
593
1280
|
{(props) => (
|
594
1281
|
<div>
|
595
|
-
<View
|
596
|
-
as="div"
|
597
|
-
padding="small"
|
598
|
-
background="primary-inverse"
|
599
|
-
>
|
1282
|
+
<View as="div" padding="small" background="primary-inverse">
|
600
1283
|
{`${selected.size} of ${rowIds.length} selected`}
|
601
1284
|
</View>
|
602
1285
|
<Table
|
603
1286
|
caption={`${caption}: sorted by ${sortBy} in ${direction} order`}
|
604
1287
|
{...props}
|
605
1288
|
>
|
606
|
-
<Table.Head
|
1289
|
+
<Table.Head
|
1290
|
+
renderSortLabel={
|
1291
|
+
<ScreenReaderContent>Sort by</ScreenReaderContent>
|
1292
|
+
}
|
1293
|
+
>
|
607
1294
|
<Table.Row>
|
608
1295
|
<Table.ColHeader id="select">
|
609
1296
|
<Checkbox
|
610
|
-
label={
|
611
|
-
|
1297
|
+
label={
|
1298
|
+
<ScreenReaderContent>Select all</ScreenReaderContent>
|
1299
|
+
}
|
1300
|
+
onChange={() => handleSelectAll(allSelected)}
|
612
1301
|
checked={allSelected}
|
613
1302
|
indeterminate={someSelected}
|
614
1303
|
/>
|
615
1304
|
</Table.ColHeader>
|
616
1305
|
{(headers || []).map(({ id, text, width }) => (
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
1306
|
+
<Table.ColHeader
|
1307
|
+
key={id}
|
1308
|
+
id={id}
|
1309
|
+
width={width}
|
1310
|
+
onRequestSort={onSort}
|
1311
|
+
sortDirection={id === sortBy ? direction : 'none'}
|
1312
|
+
>
|
1313
|
+
{text}
|
1314
|
+
</Table.ColHeader>
|
1315
|
+
))}
|
627
1316
|
</Table.Row>
|
628
1317
|
</Table.Head>
|
629
1318
|
<Table.Body>
|
@@ -634,8 +1323,12 @@ class SelectableTable extends React.Component {
|
|
634
1323
|
<Table.Row key={row.id}>
|
635
1324
|
<Table.RowHeader>
|
636
1325
|
<Checkbox
|
637
|
-
label={
|
638
|
-
|
1326
|
+
label={
|
1327
|
+
<ScreenReaderContent>
|
1328
|
+
Select row
|
1329
|
+
</ScreenReaderContent>
|
1330
|
+
}
|
1331
|
+
onChange={() => handleSelectRow(rowSelected, row.id)}
|
639
1332
|
checked={rowSelected}
|
640
1333
|
/>
|
641
1334
|
</Table.RowHeader>
|
@@ -661,34 +1354,27 @@ class SelectableTable extends React.Component {
|
|
661
1354
|
</Responsive>
|
662
1355
|
)
|
663
1356
|
}
|
664
|
-
}
|
665
1357
|
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
1358
|
+
const PaginatedTable = ({
|
1359
|
+
caption,
|
1360
|
+
headers,
|
1361
|
+
rows,
|
1362
|
+
onSort,
|
1363
|
+
sortBy,
|
1364
|
+
ascending,
|
1365
|
+
perPage
|
1366
|
+
}) => {
|
1367
|
+
const [page, setPage] = useState(0)
|
1368
|
+
|
1369
|
+
const handleClick = (page) => {
|
1370
|
+
setPage(page)
|
671
1371
|
}
|
672
|
-
}
|
673
|
-
|
674
|
-
handleClick = (page) => {
|
675
|
-
this.setState({
|
676
|
-
page,
|
677
|
-
})
|
678
|
-
}
|
679
|
-
|
680
|
-
handleSort = (event, options) => {
|
681
|
-
const { onSort } = this.props
|
682
1372
|
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
}
|
1373
|
+
const handleSort = (event, options) => {
|
1374
|
+
setPage(0)
|
1375
|
+
onSort(event, options)
|
1376
|
+
}
|
688
1377
|
|
689
|
-
render() {
|
690
|
-
const { caption, headers, rows, sortBy, ascending, perPage } = this.props
|
691
|
-
const { page } = this.state
|
692
1378
|
const startIndex = page * perPage
|
693
1379
|
const slicedRows = rows.slice(startIndex, startIndex + perPage)
|
694
1380
|
const pageCount = perPage && Math.ceil(rows.length / perPage)
|
@@ -699,22 +1385,22 @@ class PaginatedTable extends React.Component {
|
|
699
1385
|
caption={caption}
|
700
1386
|
headers={headers}
|
701
1387
|
rows={slicedRows}
|
702
|
-
onSort={
|
1388
|
+
onSort={handleSort}
|
703
1389
|
sortBy={sortBy}
|
704
1390
|
ascending={ascending}
|
705
1391
|
rowIds={rows.map((row) => row.id)}
|
706
1392
|
/>
|
707
1393
|
{pageCount > 1 && (
|
708
1394
|
<Pagination
|
709
|
-
variant=
|
710
|
-
labelNext=
|
711
|
-
labelPrev=
|
712
|
-
margin=
|
1395
|
+
variant="compact"
|
1396
|
+
labelNext="Next Page"
|
1397
|
+
labelPrev="Previous Page"
|
1398
|
+
margin="large"
|
713
1399
|
>
|
714
1400
|
{Array.from(Array(pageCount), (item, index) => (
|
715
1401
|
<Pagination.Page
|
716
1402
|
key={index}
|
717
|
-
onClick={() =>
|
1403
|
+
onClick={() => handleClick(index)}
|
718
1404
|
current={index === page}
|
719
1405
|
>
|
720
1406
|
{index + 1}
|
@@ -732,57 +1418,37 @@ class PaginatedTable extends React.Component {
|
|
732
1418
|
</div>
|
733
1419
|
)
|
734
1420
|
}
|
735
|
-
}
|
736
1421
|
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
const { headers } = props
|
741
|
-
|
742
|
-
this.state = {
|
743
|
-
sortBy: headers && headers[0] && headers[0].id,
|
744
|
-
ascending: true,
|
745
|
-
}
|
746
|
-
}
|
1422
|
+
const SortableTable = ({ caption, headers, rows, perPage }) => {
|
1423
|
+
const [sortBy, setSortBy] = useState(headers && headers[0] && headers[0].id)
|
1424
|
+
const [ascending, setAscending] = useState(true)
|
747
1425
|
|
748
|
-
|
749
|
-
|
1426
|
+
const sortedRows = useMemo(() => {
|
1427
|
+
if (!sortBy) return rows
|
750
1428
|
|
751
|
-
|
752
|
-
|
753
|
-
ascending: !ascending,
|
754
|
-
})
|
755
|
-
} else {
|
756
|
-
this.setState({
|
757
|
-
sortBy: id,
|
758
|
-
ascending: true,
|
1429
|
+
const sorted = [...rows].sort((a, b) => {
|
1430
|
+
return a[sortBy] > b[sortBy] ? 1 : a[sortBy] < b[sortBy] ? -1 : 0
|
759
1431
|
})
|
760
|
-
}
|
761
|
-
}
|
762
1432
|
|
763
|
-
|
764
|
-
|
765
|
-
const { sortBy, ascending } = this.state
|
766
|
-
const sortedRows = [...rows].sort((a, b) => {
|
767
|
-
if (a[sortBy] < b[sortBy]) {
|
768
|
-
return -1
|
769
|
-
}
|
770
|
-
if (a[sortBy] > b[sortBy]) {
|
771
|
-
return 1
|
772
|
-
}
|
773
|
-
return 0
|
774
|
-
})
|
1433
|
+
return ascending ? sorted : sorted.reverse()
|
1434
|
+
}, [sortBy, ascending, rows])
|
775
1435
|
|
776
|
-
|
777
|
-
|
1436
|
+
const handleSort = (event, { id }) => {
|
1437
|
+
if (id === sortBy) {
|
1438
|
+
setAscending(!ascending)
|
1439
|
+
} else {
|
1440
|
+
setSortBy(id)
|
1441
|
+
setAscending(true)
|
1442
|
+
}
|
778
1443
|
}
|
1444
|
+
|
779
1445
|
return (
|
780
1446
|
<div>
|
781
1447
|
<PaginatedTable
|
782
1448
|
caption={caption}
|
783
1449
|
headers={headers}
|
784
1450
|
rows={sortedRows}
|
785
|
-
onSort={
|
1451
|
+
onSort={handleSort}
|
786
1452
|
sortBy={sortBy}
|
787
1453
|
ascending={ascending}
|
788
1454
|
perPage={perPage}
|
@@ -792,86 +1458,82 @@ class SortableTable extends React.Component {
|
|
792
1458
|
liveRegionPoliteness="polite"
|
793
1459
|
screenReaderOnly
|
794
1460
|
>
|
795
|
-
{`Sorted by ${sortBy} in ${
|
1461
|
+
{`Sorted by ${sortBy} in ${
|
1462
|
+
ascending ? 'ascending' : 'descending'
|
1463
|
+
} order`}
|
796
1464
|
</Alert>
|
797
1465
|
</div>
|
798
1466
|
)
|
799
1467
|
}
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
}
|
828
|
-
{
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
},
|
870
|
-
]}
|
871
|
-
perPage={3}
|
872
|
-
/>
|
873
|
-
)
|
874
|
-
```
|
1468
|
+
|
1469
|
+
const renderRating = (rating) => (
|
1470
|
+
<Rating label="Rating" valueNow={rating} valueMax={10} iconCount={5} />
|
1471
|
+
)
|
1472
|
+
|
1473
|
+
render(
|
1474
|
+
<SortableTable
|
1475
|
+
caption="Top rated movies"
|
1476
|
+
headers={[
|
1477
|
+
{
|
1478
|
+
id: 'Rank',
|
1479
|
+
text: 'Rank'
|
1480
|
+
},
|
1481
|
+
{
|
1482
|
+
id: 'Title',
|
1483
|
+
text: 'Title',
|
1484
|
+
width: '40%'
|
1485
|
+
},
|
1486
|
+
{
|
1487
|
+
id: 'Year',
|
1488
|
+
text: 'Year'
|
1489
|
+
},
|
1490
|
+
{
|
1491
|
+
id: 'Rating',
|
1492
|
+
text: 'Rating',
|
1493
|
+
renderCell: renderRating
|
1494
|
+
}
|
1495
|
+
]}
|
1496
|
+
rows={[
|
1497
|
+
{
|
1498
|
+
id: '1',
|
1499
|
+
Rank: 1,
|
1500
|
+
Title: 'The Shawshank Redemption',
|
1501
|
+
Year: 1994,
|
1502
|
+
Rating: 9.3
|
1503
|
+
},
|
1504
|
+
{
|
1505
|
+
id: '2',
|
1506
|
+
Rank: 2,
|
1507
|
+
Title: 'The Godfather',
|
1508
|
+
Year: 1972,
|
1509
|
+
Rating: 9.2
|
1510
|
+
},
|
1511
|
+
{
|
1512
|
+
id: '3',
|
1513
|
+
Rank: 3,
|
1514
|
+
Title: 'The Godfather: Part II',
|
1515
|
+
Year: 1974,
|
1516
|
+
Rating: 9.0
|
1517
|
+
},
|
1518
|
+
{
|
1519
|
+
id: '4',
|
1520
|
+
Rank: 4,
|
1521
|
+
Title: 'The Dark Knight',
|
1522
|
+
Year: 2008,
|
1523
|
+
Rating: 9.0
|
1524
|
+
},
|
1525
|
+
{
|
1526
|
+
id: '5',
|
1527
|
+
Rank: 5,
|
1528
|
+
Title: '12 Angry Men',
|
1529
|
+
Year: 1957,
|
1530
|
+
Rating: 8.9
|
1531
|
+
}
|
1532
|
+
]}
|
1533
|
+
perPage={3}
|
1534
|
+
/>
|
1535
|
+
)
|
1536
|
+
```
|
875
1537
|
|
876
1538
|
### Using Custom Components as Children
|
877
1539
|
|
@@ -881,55 +1543,138 @@ In some cases you might want to use custom components in a `Table`, e.g. a HOC f
|
|
881
1543
|
|
882
1544
|
Wrapper HOCs are simple, just return the original component:
|
883
1545
|
|
884
|
-
```javascript
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
class CustomTableCell extends React.Component {
|
890
|
-
render () {
|
891
|
-
return (
|
892
|
-
<Table.Cell {...this.props}>{this.props.children}</Table.Cell>
|
893
|
-
)
|
1546
|
+
- ```javascript
|
1547
|
+
class CustomTableCell extends React.Component {
|
1548
|
+
render() {
|
1549
|
+
return <Table.Cell {...this.props}>{this.props.children}</Table.Cell>
|
1550
|
+
}
|
894
1551
|
}
|
895
|
-
}
|
896
1552
|
|
897
|
-
class CustomTableRow extends React.Component {
|
898
|
-
|
899
|
-
|
1553
|
+
class CustomTableRow extends React.Component {
|
1554
|
+
render() {
|
1555
|
+
return (
|
900
1556
|
<Table.Row {...this.props}>
|
901
1557
|
<Table.RowHeader>1</Table.RowHeader>
|
902
1558
|
<Table.Cell>The Shawshank Redemption</Table.Cell>
|
903
1559
|
<Table.Cell>1994</Table.Cell>
|
904
1560
|
<CustomTableCell>9.3</CustomTableCell>
|
905
1561
|
</Table.Row>
|
906
|
-
|
1562
|
+
)
|
1563
|
+
}
|
907
1564
|
}
|
908
|
-
}
|
909
1565
|
|
910
|
-
class Example extends React.Component {
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
1566
|
+
class Example extends React.Component {
|
1567
|
+
state = {
|
1568
|
+
layout: 'auto',
|
1569
|
+
hover: false
|
1570
|
+
}
|
915
1571
|
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
1572
|
+
handleChange = (field, value) => {
|
1573
|
+
this.setState({
|
1574
|
+
[field]: value
|
1575
|
+
})
|
1576
|
+
}
|
1577
|
+
|
1578
|
+
renderOptions() {
|
1579
|
+
const { layout, hover } = this.state
|
1580
|
+
|
1581
|
+
return (
|
1582
|
+
<Flex alignItems="start">
|
1583
|
+
<Flex.Item margin="small">
|
1584
|
+
<RadioInputGroup
|
1585
|
+
name="layout2"
|
1586
|
+
description="layout2"
|
1587
|
+
value={layout}
|
1588
|
+
onChange={(e, value) => this.handleChange('layout', value)}
|
1589
|
+
>
|
1590
|
+
<RadioInput label="auto" value="auto" />
|
1591
|
+
<RadioInput label="fixed" value="fixed" />
|
1592
|
+
<RadioInput label="stacked" value="stacked" />
|
1593
|
+
</RadioInputGroup>
|
1594
|
+
</Flex.Item>
|
1595
|
+
<Flex.Item margin="small">
|
1596
|
+
<Checkbox
|
1597
|
+
label="hover"
|
1598
|
+
checked={hover}
|
1599
|
+
onChange={(e, value) => this.handleChange('hover', !hover)}
|
1600
|
+
/>
|
1601
|
+
</Flex.Item>
|
1602
|
+
</Flex>
|
1603
|
+
)
|
1604
|
+
}
|
1605
|
+
|
1606
|
+
render() {
|
1607
|
+
const { layout, hover } = this.state
|
1608
|
+
return (
|
1609
|
+
<div>
|
1610
|
+
{this.renderOptions()}
|
1611
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
1612
|
+
<Table.Head>
|
1613
|
+
<Table.Row>
|
1614
|
+
<Table.ColHeader id="Rank">Rank</Table.ColHeader>
|
1615
|
+
<Table.ColHeader id="Title">Title</Table.ColHeader>
|
1616
|
+
<Table.ColHeader id="Year">Year</Table.ColHeader>
|
1617
|
+
<Table.ColHeader id="Rating">Rating</Table.ColHeader>
|
1618
|
+
</Table.Row>
|
1619
|
+
</Table.Head>
|
1620
|
+
<Table.Body>
|
1621
|
+
<CustomTableRow />
|
1622
|
+
<Table.Row>
|
1623
|
+
<Table.RowHeader>2</Table.RowHeader>
|
1624
|
+
<Table.Cell>The Godfather</Table.Cell>
|
1625
|
+
<Table.Cell>1972</Table.Cell>
|
1626
|
+
<Table.Cell>9.2</Table.Cell>
|
1627
|
+
</Table.Row>
|
1628
|
+
<Table.Row>
|
1629
|
+
<Table.RowHeader>3</Table.RowHeader>
|
1630
|
+
<Table.Cell>The Godfather: Part II</Table.Cell>
|
1631
|
+
<Table.Cell>1974</Table.Cell>
|
1632
|
+
<Table.Cell>9.0</Table.Cell>
|
1633
|
+
</Table.Row>
|
1634
|
+
</Table.Body>
|
1635
|
+
</Table>
|
1636
|
+
</div>
|
1637
|
+
)
|
1638
|
+
}
|
920
1639
|
}
|
921
1640
|
|
922
|
-
|
923
|
-
|
1641
|
+
render(<Example />)
|
1642
|
+
```
|
924
1643
|
|
925
|
-
|
1644
|
+
- ```javascript
|
1645
|
+
const CustomTableCell = ({ children, ...props }) => (
|
1646
|
+
<Table.Cell {...props}>{children}</Table.Cell>
|
1647
|
+
)
|
1648
|
+
|
1649
|
+
const CustomTableRow = ({ children, ...props }) => (
|
1650
|
+
<Table.Row {...props}>
|
1651
|
+
<Table.RowHeader>1</Table.RowHeader>
|
1652
|
+
<Table.Cell>The Shawshank Redemption</Table.Cell>
|
1653
|
+
<Table.Cell>1994</Table.Cell>
|
1654
|
+
<CustomTableCell>9.3</CustomTableCell>
|
1655
|
+
</Table.Row>
|
1656
|
+
)
|
1657
|
+
|
1658
|
+
const Example = () => {
|
1659
|
+
const [layout, setLayout] = useState('auto')
|
1660
|
+
const [hover, setHover] = useState(false)
|
1661
|
+
|
1662
|
+
const handleChange = (field, value) => {
|
1663
|
+
if (field === 'layout') {
|
1664
|
+
setLayout(value)
|
1665
|
+
} else if (field === 'hover') {
|
1666
|
+
setHover(!hover)
|
1667
|
+
}
|
1668
|
+
}
|
1669
|
+
|
1670
|
+
const renderOptions = () => (
|
926
1671
|
<Flex alignItems="start">
|
927
1672
|
<Flex.Item margin="small">
|
928
1673
|
<RadioInputGroup
|
929
1674
|
name="layout2"
|
930
1675
|
description="layout2"
|
931
1676
|
value={layout}
|
932
|
-
onChange={(e, value) =>
|
1677
|
+
onChange={(e, value) => handleChange('layout', value)}
|
933
1678
|
>
|
934
1679
|
<RadioInput label="auto" value="auto" />
|
935
1680
|
<RadioInput label="fixed" value="fixed" />
|
@@ -940,23 +1685,16 @@ class Example extends React.Component {
|
|
940
1685
|
<Checkbox
|
941
1686
|
label="hover"
|
942
1687
|
checked={hover}
|
943
|
-
onChange={(e, value) =>
|
1688
|
+
onChange={(e, value) => handleChange('hover', !hover)}
|
944
1689
|
/>
|
945
1690
|
</Flex.Item>
|
946
1691
|
</Flex>
|
947
1692
|
)
|
948
|
-
}
|
949
1693
|
|
950
|
-
render() {
|
951
|
-
const { layout, hover } = this.state
|
952
1694
|
return (
|
953
1695
|
<div>
|
954
|
-
{
|
955
|
-
<Table
|
956
|
-
caption='Top rated movies'
|
957
|
-
layout={layout}
|
958
|
-
hover={hover}
|
959
|
-
>
|
1696
|
+
{renderOptions()}
|
1697
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
960
1698
|
<Table.Head>
|
961
1699
|
<Table.Row>
|
962
1700
|
<Table.ColHeader id="Rank">Rank</Table.ColHeader>
|
@@ -966,7 +1704,7 @@ class Example extends React.Component {
|
|
966
1704
|
</Table.Row>
|
967
1705
|
</Table.Head>
|
968
1706
|
<Table.Body>
|
969
|
-
<CustomTableRow/>
|
1707
|
+
<CustomTableRow />
|
970
1708
|
<Table.Row>
|
971
1709
|
<Table.RowHeader>2</Table.RowHeader>
|
972
1710
|
<Table.Cell>The Godfather</Table.Cell>
|
@@ -984,10 +1722,9 @@ class Example extends React.Component {
|
|
984
1722
|
</div>
|
985
1723
|
)
|
986
1724
|
}
|
987
|
-
}
|
988
1725
|
|
989
|
-
render(<Example />)
|
990
|
-
```
|
1726
|
+
render(<Example />)
|
1727
|
+
```
|
991
1728
|
|
992
1729
|
#### Fully custom components
|
993
1730
|
|
@@ -1000,64 +1737,163 @@ If you want to use fully custom components you have to pay attention to the foll
|
|
1000
1737
|
|
1001
1738
|
Basic fully custom table:
|
1002
1739
|
|
1003
|
-
```javascript
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
class CustomTableCell extends React.Component {
|
1009
|
-
render() {
|
1010
|
-
return <td>{this.props.children}</td>
|
1740
|
+
- ```javascript
|
1741
|
+
class CustomTableCell extends React.Component {
|
1742
|
+
render() {
|
1743
|
+
return <td>{this.props.children}</td>
|
1744
|
+
}
|
1011
1745
|
}
|
1012
|
-
}
|
1013
1746
|
|
1014
|
-
class CustomTableRow extends React.Component {
|
1015
|
-
|
1016
|
-
|
1747
|
+
class CustomTableRow extends React.Component {
|
1748
|
+
static contextType = TableContext
|
1749
|
+
state = { isHovered: false }
|
1017
1750
|
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1751
|
+
toggleHoverOff = () => {
|
1752
|
+
this.setState({ isHovered: false })
|
1753
|
+
}
|
1754
|
+
toggleHoverOn = () => {
|
1755
|
+
this.setState({ isHovered: true })
|
1756
|
+
}
|
1757
|
+
|
1758
|
+
render() {
|
1759
|
+
const rowStyle =
|
1760
|
+
this.context.hover && this.state.isHovered
|
1761
|
+
? { backgroundColor: 'SeaGreen' }
|
1762
|
+
: { backgroundColor: 'white' }
|
1763
|
+
return (
|
1764
|
+
<tr
|
1765
|
+
style={rowStyle}
|
1766
|
+
onMouseOver={this.toggleHoverOn}
|
1767
|
+
onMouseOut={this.toggleHoverOff}
|
1768
|
+
>
|
1769
|
+
{this.props.children}
|
1770
|
+
</tr>
|
1771
|
+
)
|
1772
|
+
}
|
1023
1773
|
}
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1774
|
+
|
1775
|
+
class Example extends React.Component {
|
1776
|
+
state = {
|
1777
|
+
layout: 'auto',
|
1778
|
+
hover: false
|
1779
|
+
}
|
1780
|
+
|
1781
|
+
handleChange = (field, value) => {
|
1782
|
+
this.setState({
|
1783
|
+
[field]: value
|
1784
|
+
})
|
1029
1785
|
}
|
1786
|
+
|
1787
|
+
renderOptions() {
|
1788
|
+
const { layout, hover } = this.state
|
1789
|
+
|
1790
|
+
return (
|
1791
|
+
<Flex alignItems="start">
|
1792
|
+
<Flex.Item margin="small">
|
1793
|
+
<RadioInputGroup
|
1794
|
+
name="Layout"
|
1795
|
+
description="Layout"
|
1796
|
+
value={layout}
|
1797
|
+
onChange={(e, value) => this.handleChange('layout', value)}
|
1798
|
+
>
|
1799
|
+
<RadioInput label="auto" value="auto" />
|
1800
|
+
<RadioInput label="fixed" value="fixed" />
|
1801
|
+
</RadioInputGroup>
|
1802
|
+
</Flex.Item>
|
1803
|
+
<Flex.Item margin="small">
|
1804
|
+
<Checkbox
|
1805
|
+
label="hover"
|
1806
|
+
checked={hover}
|
1807
|
+
onChange={(e, value) => this.handleChange('hover', !hover)}
|
1808
|
+
/>
|
1809
|
+
</Flex.Item>
|
1810
|
+
</Flex>
|
1811
|
+
)
|
1812
|
+
}
|
1813
|
+
|
1814
|
+
render() {
|
1815
|
+
const { layout, hover } = this.state
|
1816
|
+
|
1817
|
+
return (
|
1818
|
+
<div>
|
1819
|
+
{this.renderOptions()}
|
1820
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
1821
|
+
<Table.Head>
|
1822
|
+
<CustomTableRow>
|
1823
|
+
<CustomTableCell scope="col">Rank</CustomTableCell>
|
1824
|
+
<CustomTableCell scope="col">Title</CustomTableCell>
|
1825
|
+
<CustomTableCell scope="col">Year</CustomTableCell>
|
1826
|
+
<CustomTableCell scope="col">Rating</CustomTableCell>
|
1827
|
+
</CustomTableRow>
|
1828
|
+
</Table.Head>
|
1829
|
+
<Table.Body>
|
1830
|
+
<CustomTableRow>
|
1831
|
+
<CustomTableCell scope="row">1</CustomTableCell>
|
1832
|
+
<CustomTableCell>The Godfather</CustomTableCell>
|
1833
|
+
<CustomTableCell>1972</CustomTableCell>
|
1834
|
+
<CustomTableCell>9.2</CustomTableCell>
|
1835
|
+
</CustomTableRow>
|
1836
|
+
<CustomTableRow>
|
1837
|
+
<CustomTableCell scope="row">2</CustomTableCell>
|
1838
|
+
<CustomTableCell>The Godfather: Part II</CustomTableCell>
|
1839
|
+
<CustomTableCell>1974</CustomTableCell>
|
1840
|
+
<CustomTableCell>9.0</CustomTableCell>
|
1841
|
+
</CustomTableRow>
|
1842
|
+
</Table.Body>
|
1843
|
+
</Table>
|
1844
|
+
</div>
|
1845
|
+
)
|
1846
|
+
}
|
1847
|
+
}
|
1848
|
+
|
1849
|
+
render(<Example />)
|
1850
|
+
```
|
1851
|
+
|
1852
|
+
- ```javascript
|
1853
|
+
const CustomTableCell = ({ children, ...props }) => (
|
1854
|
+
<td {...props}>{children}</td>
|
1855
|
+
)
|
1856
|
+
|
1857
|
+
const CustomTableRow = ({ children, ...props }) => {
|
1858
|
+
const { hover } = useContext(TableContext)
|
1859
|
+
const [isHovered, setIsHovered] = useState(false)
|
1860
|
+
|
1861
|
+
const rowStyle =
|
1862
|
+
hover && isHovered
|
1863
|
+
? { backgroundColor: 'SeaGreen' }
|
1864
|
+
: { backgroundColor: 'white' }
|
1865
|
+
|
1030
1866
|
return (
|
1031
|
-
<tr
|
1032
|
-
{
|
1867
|
+
<tr
|
1868
|
+
style={rowStyle}
|
1869
|
+
onMouseOver={() => setIsHovered(true)}
|
1870
|
+
onMouseOut={() => setIsHovered(false)}
|
1871
|
+
>
|
1872
|
+
{children}
|
1033
1873
|
</tr>
|
1034
1874
|
)
|
1035
1875
|
}
|
1036
|
-
}
|
1037
1876
|
|
1038
|
-
|
1039
|
-
|
1040
|
-
|
1041
|
-
hover: false,
|
1042
|
-
}
|
1877
|
+
const Example = () => {
|
1878
|
+
const [layout, setLayout] = useState('auto')
|
1879
|
+
const [hover, setHover] = useState(false)
|
1043
1880
|
|
1044
|
-
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
const { layout, hover } = this.state
|
1881
|
+
const handleChange = (field, value) => {
|
1882
|
+
if (field === 'layout') {
|
1883
|
+
setLayout(value)
|
1884
|
+
} else if (field === 'hover') {
|
1885
|
+
setHover(!hover)
|
1886
|
+
}
|
1887
|
+
}
|
1052
1888
|
|
1053
|
-
|
1889
|
+
const renderOptions = () => (
|
1054
1890
|
<Flex alignItems="start">
|
1055
1891
|
<Flex.Item margin="small">
|
1056
1892
|
<RadioInputGroup
|
1057
1893
|
name="Layout"
|
1058
1894
|
description="Layout"
|
1059
1895
|
value={layout}
|
1060
|
-
onChange={(e, value) =>
|
1896
|
+
onChange={(e, value) => handleChange('layout', value)}
|
1061
1897
|
>
|
1062
1898
|
<RadioInput label="auto" value="auto" />
|
1063
1899
|
<RadioInput label="fixed" value="fixed" />
|
@@ -1067,41 +1903,33 @@ class Example extends React.Component {
|
|
1067
1903
|
<Checkbox
|
1068
1904
|
label="hover"
|
1069
1905
|
checked={hover}
|
1070
|
-
onChange={(e, value) =>
|
1906
|
+
onChange={(e, value) => handleChange('hover', !hover)}
|
1071
1907
|
/>
|
1072
1908
|
</Flex.Item>
|
1073
1909
|
</Flex>
|
1074
1910
|
)
|
1075
|
-
}
|
1076
|
-
|
1077
|
-
render() {
|
1078
|
-
const { layout, hover } = this.state
|
1079
1911
|
|
1080
1912
|
return (
|
1081
1913
|
<div>
|
1082
|
-
{
|
1083
|
-
<Table
|
1084
|
-
caption='Top rated movies'
|
1085
|
-
layout={layout}
|
1086
|
-
hover={hover}
|
1087
|
-
>
|
1914
|
+
{renderOptions()}
|
1915
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
1088
1916
|
<Table.Head>
|
1089
1917
|
<CustomTableRow>
|
1090
|
-
<CustomTableCell scope=
|
1091
|
-
<CustomTableCell scope=
|
1092
|
-
<CustomTableCell scope=
|
1093
|
-
<CustomTableCell scope=
|
1918
|
+
<CustomTableCell scope="col">Rank</CustomTableCell>
|
1919
|
+
<CustomTableCell scope="col">Title</CustomTableCell>
|
1920
|
+
<CustomTableCell scope="col">Year</CustomTableCell>
|
1921
|
+
<CustomTableCell scope="col">Rating</CustomTableCell>
|
1094
1922
|
</CustomTableRow>
|
1095
1923
|
</Table.Head>
|
1096
1924
|
<Table.Body>
|
1097
1925
|
<CustomTableRow>
|
1098
|
-
<CustomTableCell scope=
|
1926
|
+
<CustomTableCell scope="row">1</CustomTableCell>
|
1099
1927
|
<CustomTableCell>The Godfather</CustomTableCell>
|
1100
1928
|
<CustomTableCell>1972</CustomTableCell>
|
1101
1929
|
<CustomTableCell>9.2</CustomTableCell>
|
1102
1930
|
</CustomTableRow>
|
1103
1931
|
<CustomTableRow>
|
1104
|
-
<CustomTableCell scope=
|
1932
|
+
<CustomTableCell scope="row">2</CustomTableCell>
|
1105
1933
|
<CustomTableCell>The Godfather: Part II</CustomTableCell>
|
1106
1934
|
<CustomTableCell>1974</CustomTableCell>
|
1107
1935
|
<CustomTableCell>9.0</CustomTableCell>
|
@@ -1111,10 +1939,9 @@ class Example extends React.Component {
|
|
1111
1939
|
</div>
|
1112
1940
|
)
|
1113
1941
|
}
|
1114
|
-
}
|
1115
1942
|
|
1116
|
-
render(<Example />)
|
1117
|
-
```
|
1943
|
+
render(<Example />)
|
1944
|
+
```
|
1118
1945
|
|
1119
1946
|
#### Fully custom components with `stacked` layout
|
1120
1947
|
|
@@ -1136,56 +1963,188 @@ Also you need the following props on the components:
|
|
1136
1963
|
|
1137
1964
|
Custom table with `stacked` layout support:
|
1138
1965
|
|
1139
|
-
```javascript
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1966
|
+
- ```javascript
|
1967
|
+
class CustomTableCell extends React.Component {
|
1968
|
+
static contextType = TableContext
|
1969
|
+
|
1970
|
+
render() {
|
1971
|
+
const isStacked = this.context.isStacked
|
1972
|
+
if (isStacked) {
|
1973
|
+
let headerTxt
|
1974
|
+
if (typeof this.props.header === 'function') {
|
1975
|
+
headerTxt = React.createElement(this.props.header)
|
1976
|
+
} else {
|
1977
|
+
headerTxt = this.props.header
|
1978
|
+
}
|
1979
|
+
return (
|
1980
|
+
<div role="cell">
|
1981
|
+
{headerTxt && headerTxt}
|
1982
|
+
{headerTxt && ': '}
|
1983
|
+
{this.props.children}
|
1984
|
+
</div>
|
1985
|
+
)
|
1986
|
+
}
|
1987
|
+
return <td>{this.props.children}</td>
|
1988
|
+
}
|
1989
|
+
}
|
1990
|
+
|
1991
|
+
class CustomTableRow extends React.Component {
|
1992
|
+
static contextType = TableContext
|
1993
|
+
state = { isHovered: false }
|
1994
|
+
|
1995
|
+
toggleHoverOff = () => {
|
1996
|
+
this.setState({ isHovered: false })
|
1997
|
+
}
|
1998
|
+
toggleHoverOn = () => {
|
1999
|
+
this.setState({ isHovered: true })
|
2000
|
+
}
|
2001
|
+
|
2002
|
+
render() {
|
2003
|
+
const { hover, headers, isStacked } = this.context
|
2004
|
+
const Tag = isStacked ? 'div' : 'tr'
|
2005
|
+
const rowStyle =
|
2006
|
+
hover && this.state.isHovered
|
2007
|
+
? { backgroundColor: 'SeaGreen' }
|
2008
|
+
: { backgroundColor: 'white' }
|
2009
|
+
|
2010
|
+
return (
|
2011
|
+
<Tag
|
2012
|
+
style={rowStyle}
|
2013
|
+
role={isStacked ? 'row' : undefined}
|
2014
|
+
onMouseOver={this.toggleHoverOn}
|
2015
|
+
onMouseOut={this.toggleHoverOff}
|
2016
|
+
>
|
2017
|
+
{React.Children.toArray(this.props.children)
|
2018
|
+
.filter(React.isValidElement)
|
2019
|
+
.map((child, index) => {
|
2020
|
+
return React.cloneElement(child, {
|
2021
|
+
key: child.props.name,
|
2022
|
+
// used by `CustomTableCell` to render its column title in `stacked` layout
|
2023
|
+
header: headers && headers[index]
|
2024
|
+
})
|
2025
|
+
})}
|
2026
|
+
</Tag>
|
2027
|
+
)
|
2028
|
+
}
|
2029
|
+
}
|
2030
|
+
|
2031
|
+
class Example extends React.Component {
|
2032
|
+
state = {
|
2033
|
+
layout: 'auto',
|
2034
|
+
hover: false
|
2035
|
+
}
|
2036
|
+
|
2037
|
+
handleChange = (field, value) => {
|
2038
|
+
this.setState({
|
2039
|
+
[field]: value
|
2040
|
+
})
|
2041
|
+
}
|
1143
2042
|
|
1144
|
-
|
1145
|
-
|
2043
|
+
renderOptions() {
|
2044
|
+
const { layout, hover } = this.state
|
2045
|
+
|
2046
|
+
return (
|
2047
|
+
<Flex alignItems="start">
|
2048
|
+
<Flex.Item margin="small">
|
2049
|
+
<RadioInputGroup
|
2050
|
+
name="customStackedLayout"
|
2051
|
+
description="Layout"
|
2052
|
+
value={layout}
|
2053
|
+
onChange={(e, value) => this.handleChange('layout', value)}
|
2054
|
+
>
|
2055
|
+
<RadioInput label="auto" value="auto" />
|
2056
|
+
<RadioInput label="fixed" value="fixed" />
|
2057
|
+
<RadioInput label="stacked" value="stacked" />
|
2058
|
+
</RadioInputGroup>
|
2059
|
+
</Flex.Item>
|
2060
|
+
<Flex.Item margin="small">
|
2061
|
+
<Checkbox
|
2062
|
+
label="hover"
|
2063
|
+
checked={hover}
|
2064
|
+
onChange={(e, value) => this.handleChange('hover', !hover)}
|
2065
|
+
/>
|
2066
|
+
</Flex.Item>
|
2067
|
+
</Flex>
|
2068
|
+
)
|
2069
|
+
}
|
1146
2070
|
|
1147
|
-
|
1148
|
-
|
2071
|
+
render() {
|
2072
|
+
const { layout, hover } = this.state
|
2073
|
+
|
2074
|
+
return (
|
2075
|
+
<div>
|
2076
|
+
{this.renderOptions()}
|
2077
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
2078
|
+
<Table.Head>
|
2079
|
+
<CustomTableRow>
|
2080
|
+
<CustomTableCell scope="col">Rank</CustomTableCell>
|
2081
|
+
<CustomTableCell scope="col">Title</CustomTableCell>
|
2082
|
+
<CustomTableCell scope="col">Year</CustomTableCell>
|
2083
|
+
<CustomTableCell scope="col">Rating</CustomTableCell>
|
2084
|
+
</CustomTableRow>
|
2085
|
+
</Table.Head>
|
2086
|
+
<Table.Body>
|
2087
|
+
<CustomTableRow>
|
2088
|
+
<CustomTableCell scope="row">1</CustomTableCell>
|
2089
|
+
<CustomTableCell>The Godfather</CustomTableCell>
|
2090
|
+
<CustomTableCell>1972</CustomTableCell>
|
2091
|
+
<CustomTableCell>9.2</CustomTableCell>
|
2092
|
+
</CustomTableRow>
|
2093
|
+
<CustomTableRow>
|
2094
|
+
<CustomTableCell scope="row">2</CustomTableCell>
|
2095
|
+
<CustomTableCell>The Godfather: Part II</CustomTableCell>
|
2096
|
+
<CustomTableCell>1974</CustomTableCell>
|
2097
|
+
<CustomTableCell>9.0</CustomTableCell>
|
2098
|
+
</CustomTableRow>
|
2099
|
+
</Table.Body>
|
2100
|
+
</Table>
|
2101
|
+
</div>
|
2102
|
+
)
|
2103
|
+
}
|
2104
|
+
}
|
2105
|
+
|
2106
|
+
render(<Example />)
|
2107
|
+
```
|
2108
|
+
|
2109
|
+
- ```javascript
|
2110
|
+
const CustomTableCell = ({ children, header }) => {
|
2111
|
+
const { isStacked } = useContext(TableContext)
|
1149
2112
|
if (isStacked) {
|
1150
2113
|
let headerTxt
|
1151
|
-
if (typeof
|
1152
|
-
headerTxt = React.createElement(
|
2114
|
+
if (typeof header === 'function') {
|
2115
|
+
headerTxt = React.createElement(header)
|
1153
2116
|
} else {
|
1154
|
-
headerTxt =
|
2117
|
+
headerTxt = header
|
1155
2118
|
}
|
1156
|
-
return
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
2119
|
+
return (
|
2120
|
+
<div role="cell">
|
2121
|
+
{headerTxt && headerTxt}
|
2122
|
+
{headerTxt && ': '}
|
2123
|
+
{children}
|
2124
|
+
</div>
|
2125
|
+
)
|
1161
2126
|
}
|
1162
|
-
return <td>{
|
2127
|
+
return <td>{children}</td>
|
1163
2128
|
}
|
1164
|
-
}
|
1165
2129
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
2130
|
+
const CustomTableRow = ({ children }) => {
|
2131
|
+
const { hover, headers, isStacked } = useContext(TableContext)
|
2132
|
+
const [isHovered, setIsHovered] = useState(false)
|
1169
2133
|
|
1170
|
-
toggleHoverOff = () => {
|
1171
|
-
this.setState({isHovered: false})
|
1172
|
-
}
|
1173
|
-
toggleHoverOn = () => {
|
1174
|
-
this.setState({isHovered: true})
|
1175
|
-
}
|
1176
|
-
render() {
|
1177
|
-
// super ugly way to change CSS on hover
|
1178
|
-
let css = {backgroundColor: 'white'}
|
1179
|
-
if (this.context.hover && this.state.isHovered) {
|
1180
|
-
css = {backgroundColor: 'SeaGreen'}
|
1181
|
-
}
|
1182
|
-
const headers = this.context.headers
|
1183
|
-
const isStacked = this.context.isStacked
|
1184
2134
|
const Tag = isStacked ? 'div' : 'tr'
|
2135
|
+
const rowStyle =
|
2136
|
+
hover && isHovered
|
2137
|
+
? { backgroundColor: 'SeaGreen' }
|
2138
|
+
: { backgroundColor: 'white' }
|
2139
|
+
|
1185
2140
|
return (
|
1186
|
-
<Tag
|
1187
|
-
|
1188
|
-
{
|
2141
|
+
<Tag
|
2142
|
+
style={rowStyle}
|
2143
|
+
role={isStacked ? 'row' : undefined}
|
2144
|
+
onMouseOver={() => setIsHovered(true)}
|
2145
|
+
onMouseOut={() => setIsHovered(false)}
|
2146
|
+
>
|
2147
|
+
{React.Children.toArray(children)
|
1189
2148
|
.filter(React.isValidElement)
|
1190
2149
|
.map((child, index) => {
|
1191
2150
|
return React.cloneElement(child, {
|
@@ -1193,36 +2152,31 @@ class CustomTableRow extends React.Component {
|
|
1193
2152
|
// used by `CustomTableCell` to render its column title in `stacked` layout
|
1194
2153
|
header: headers && headers[index]
|
1195
2154
|
})
|
1196
|
-
})
|
1197
|
-
}
|
2155
|
+
})}
|
1198
2156
|
</Tag>
|
1199
2157
|
)
|
1200
2158
|
}
|
1201
|
-
}
|
1202
2159
|
|
1203
|
-
|
1204
|
-
|
1205
|
-
|
1206
|
-
hover: false,
|
1207
|
-
}
|
1208
|
-
|
1209
|
-
handleChange = (field, value) => {
|
1210
|
-
this.setState({
|
1211
|
-
[field]: value,
|
1212
|
-
})
|
1213
|
-
}
|
2160
|
+
const Example = () => {
|
2161
|
+
const [layout, setLayout] = useState('auto')
|
2162
|
+
const [hover, setHover] = useState(false)
|
1214
2163
|
|
1215
|
-
|
1216
|
-
|
2164
|
+
const handleChange = (field, value) => {
|
2165
|
+
if (field === 'layout') {
|
2166
|
+
setLayout(value)
|
2167
|
+
} else if (field === 'hover') {
|
2168
|
+
setHover(!hover)
|
2169
|
+
}
|
2170
|
+
}
|
1217
2171
|
|
1218
|
-
|
2172
|
+
const renderOptions = () => (
|
1219
2173
|
<Flex alignItems="start">
|
1220
2174
|
<Flex.Item margin="small">
|
1221
2175
|
<RadioInputGroup
|
1222
2176
|
name="customStackedLayout"
|
1223
2177
|
description="Layout"
|
1224
2178
|
value={layout}
|
1225
|
-
onChange={(e, value) =>
|
2179
|
+
onChange={(e, value) => handleChange('layout', value)}
|
1226
2180
|
>
|
1227
2181
|
<RadioInput label="auto" value="auto" />
|
1228
2182
|
<RadioInput label="fixed" value="fixed" />
|
@@ -1233,41 +2187,33 @@ class Example extends React.Component {
|
|
1233
2187
|
<Checkbox
|
1234
2188
|
label="hover"
|
1235
2189
|
checked={hover}
|
1236
|
-
onChange={(e, value) =>
|
2190
|
+
onChange={(e, value) => handleChange('hover', !hover)}
|
1237
2191
|
/>
|
1238
2192
|
</Flex.Item>
|
1239
2193
|
</Flex>
|
1240
2194
|
)
|
1241
|
-
}
|
1242
|
-
|
1243
|
-
render() {
|
1244
|
-
const { layout, hover } = this.state
|
1245
2195
|
|
1246
2196
|
return (
|
1247
2197
|
<div>
|
1248
|
-
{
|
1249
|
-
<Table
|
1250
|
-
caption='Top rated movies'
|
1251
|
-
layout={layout}
|
1252
|
-
hover={hover}
|
1253
|
-
>
|
2198
|
+
{renderOptions()}
|
2199
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
1254
2200
|
<Table.Head>
|
1255
2201
|
<CustomTableRow>
|
1256
|
-
<CustomTableCell scope=
|
1257
|
-
<CustomTableCell scope=
|
1258
|
-
<CustomTableCell scope=
|
1259
|
-
<CustomTableCell scope=
|
2202
|
+
<CustomTableCell scope="col">Rank</CustomTableCell>
|
2203
|
+
<CustomTableCell scope="col">Title</CustomTableCell>
|
2204
|
+
<CustomTableCell scope="col">Year</CustomTableCell>
|
2205
|
+
<CustomTableCell scope="col">Rating</CustomTableCell>
|
1260
2206
|
</CustomTableRow>
|
1261
2207
|
</Table.Head>
|
1262
2208
|
<Table.Body>
|
1263
2209
|
<CustomTableRow>
|
1264
|
-
<CustomTableCell scope=
|
2210
|
+
<CustomTableCell scope="row">1</CustomTableCell>
|
1265
2211
|
<CustomTableCell>The Godfather</CustomTableCell>
|
1266
2212
|
<CustomTableCell>1972</CustomTableCell>
|
1267
2213
|
<CustomTableCell>9.2</CustomTableCell>
|
1268
2214
|
</CustomTableRow>
|
1269
2215
|
<CustomTableRow>
|
1270
|
-
<CustomTableCell scope=
|
2216
|
+
<CustomTableCell scope="row">2</CustomTableCell>
|
1271
2217
|
<CustomTableCell>The Godfather: Part II</CustomTableCell>
|
1272
2218
|
<CustomTableCell>1974</CustomTableCell>
|
1273
2219
|
<CustomTableCell>9.0</CustomTableCell>
|
@@ -1277,10 +2223,9 @@ class Example extends React.Component {
|
|
1277
2223
|
</div>
|
1278
2224
|
)
|
1279
2225
|
}
|
1280
|
-
}
|
1281
2226
|
|
1282
|
-
render(<Example />)
|
1283
|
-
```
|
2227
|
+
render(<Example />)
|
2228
|
+
```
|
1284
2229
|
|
1285
2230
|
### Guidelines
|
1286
2231
|
|