@instructure/ui-table 9.7.0 → 9.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/es/Table/Body/index.js +24 -11
- package/es/Table/Body/props.js +2 -5
- package/es/Table/Body/styles.js +0 -2
- package/es/Table/Cell/index.js +3 -2
- package/es/Table/Cell/props.js +1 -2
- package/es/Table/ColHeader/props.js +2 -1
- package/es/Table/Head/index.js +40 -30
- package/es/Table/Head/props.js +1 -2
- package/es/Table/Head/styles.js +0 -2
- package/es/Table/Row/index.js +25 -12
- package/es/Table/Row/props.js +2 -5
- package/es/Table/Row/styles.js +4 -6
- package/es/Table/RowHeader/index.js +3 -2
- package/es/Table/RowHeader/props.js +1 -2
- package/es/Table/TableContext.js +34 -0
- package/es/Table/__new-tests__/Table.test.js +74 -1
- package/es/Table/index.js +25 -17
- package/es/index.js +2 -1
- package/lib/Table/Body/index.js +23 -10
- package/lib/Table/Body/props.js +2 -5
- package/lib/Table/Body/styles.js +0 -2
- package/lib/Table/Cell/index.js +3 -2
- package/lib/Table/Cell/props.js +1 -2
- package/lib/Table/ColHeader/props.js +2 -1
- package/lib/Table/Head/index.js +39 -30
- package/lib/Table/Head/props.js +1 -2
- package/lib/Table/Head/styles.js +0 -2
- package/lib/Table/Row/index.js +24 -11
- package/lib/Table/Row/props.js +2 -5
- package/lib/Table/Row/styles.js +4 -6
- package/lib/Table/RowHeader/index.js +3 -2
- package/lib/Table/RowHeader/props.js +1 -2
- package/lib/Table/TableContext.js +39 -0
- package/lib/Table/__new-tests__/Table.test.js +74 -1
- package/lib/Table/index.js +24 -16
- package/lib/index.js +8 -1
- package/package.json +17 -17
- package/src/Table/Body/index.tsx +23 -13
- package/src/Table/Body/props.ts +6 -18
- package/src/Table/Body/styles.ts +0 -2
- package/src/Table/Cell/index.tsx +6 -3
- package/src/Table/Cell/props.ts +7 -9
- package/src/Table/ColHeader/props.ts +9 -3
- package/src/Table/Head/index.tsx +40 -40
- package/src/Table/Head/props.ts +20 -10
- package/src/Table/Head/styles.ts +0 -2
- package/src/Table/README.md +1788 -546
- package/src/Table/Row/index.tsx +27 -11
- package/src/Table/Row/props.ts +7 -19
- package/src/Table/Row/styles.ts +5 -6
- package/src/Table/RowHeader/index.tsx +6 -4
- package/src/Table/RowHeader/props.ts +1 -3
- package/src/Table/TableContext.ts +54 -0
- package/src/Table/__new-tests__/Table.test.tsx +131 -2
- package/src/Table/index.tsx +42 -44
- package/src/Table/props.ts +8 -28
- package/src/index.ts +1 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Table/Body/index.d.ts +6 -13
- package/types/Table/Body/index.d.ts.map +1 -1
- package/types/Table/Body/props.d.ts +4 -5
- package/types/Table/Body/props.d.ts.map +1 -1
- package/types/Table/Body/styles.d.ts +0 -2
- package/types/Table/Body/styles.d.ts.map +1 -1
- package/types/Table/Cell/index.d.ts +4 -3
- package/types/Table/Cell/index.d.ts.map +1 -1
- package/types/Table/Cell/props.d.ts +6 -2
- package/types/Table/Cell/props.d.ts.map +1 -1
- package/types/Table/ColHeader/index.d.ts +2 -0
- package/types/Table/ColHeader/index.d.ts.map +1 -1
- package/types/Table/ColHeader/props.d.ts +7 -3
- package/types/Table/ColHeader/props.d.ts.map +1 -1
- package/types/Table/Head/index.d.ts +15 -5
- package/types/Table/Head/index.d.ts.map +1 -1
- package/types/Table/Head/props.d.ts +19 -4
- package/types/Table/Head/props.d.ts.map +1 -1
- package/types/Table/Head/styles.d.ts +0 -2
- package/types/Table/Head/styles.d.ts.map +1 -1
- package/types/Table/Row/index.d.ts +6 -13
- package/types/Table/Row/index.d.ts.map +1 -1
- package/types/Table/Row/props.d.ts +5 -6
- package/types/Table/Row/props.d.ts.map +1 -1
- package/types/Table/Row/styles.d.ts +5 -2
- package/types/Table/Row/styles.d.ts.map +1 -1
- package/types/Table/RowHeader/index.d.ts +4 -3
- package/types/Table/RowHeader/index.d.ts.map +1 -1
- package/types/Table/RowHeader/props.d.ts +0 -1
- package/types/Table/RowHeader/props.d.ts.map +1 -1
- package/types/Table/TableContext.d.ts +24 -0
- package/types/Table/TableContext.d.ts.map +1 -0
- package/types/Table/index.d.ts +1 -1
- package/types/Table/index.d.ts.map +1 -1
- package/types/Table/props.d.ts +10 -22
- package/types/Table/props.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +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
|
+
}
|
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
|
+
```
|
118
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
|
+
}
|
253
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
|
+
}
|
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
1372
|
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
}
|
678
|
-
}
|
679
|
-
|
680
|
-
handleSort = (event, options) => {
|
681
|
-
const { onSort } = this.props
|
682
|
-
|
683
|
-
this.setState({
|
684
|
-
page: 0,
|
685
|
-
})
|
686
|
-
onSort(event, options)
|
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
|
-
|
737
|
-
class SortableTable extends React.Component {
|
738
|
-
constructor (props) {
|
739
|
-
super(props)
|
740
|
-
const { headers } = props
|
741
1421
|
|
742
|
-
|
743
|
-
|
744
|
-
|
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,139 +1458,223 @@ 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
|
|
878
|
-
In some cases you might want to use custom components in a `Table`, e.g. a HOC for `Table.Row` or `Table.Cell`. This is generally not recommended but sometimes it could be beneficial for codesplitting or writing cleaner code for larger and more complex Tables.
|
1540
|
+
In some cases you might want to use custom components in a `Table`, e.g. a HOC for `Table.Row` or `Table.Cell`. This is generally not recommended, but sometimes it could be beneficial for codesplitting or writing cleaner code for larger and more complex Tables.
|
879
1541
|
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
1542
|
+
> Do not replace `Table.Body` and `Table.Head` with custom components
|
1543
|
+
|
1544
|
+
Wrapper HOCs are simple, just return the original component:
|
1545
|
+
|
1546
|
+
- ```javascript
|
1547
|
+
class CustomTableCell extends React.Component {
|
1548
|
+
render() {
|
1549
|
+
return <Table.Cell {...this.props}>{this.props.children}</Table.Cell>
|
1550
|
+
}
|
889
1551
|
}
|
890
|
-
}
|
891
1552
|
|
892
|
-
class CustomTableRow extends React.Component {
|
893
|
-
|
894
|
-
|
1553
|
+
class CustomTableRow extends React.Component {
|
1554
|
+
render() {
|
1555
|
+
return (
|
895
1556
|
<Table.Row {...this.props}>
|
896
1557
|
<Table.RowHeader>1</Table.RowHeader>
|
897
1558
|
<Table.Cell>The Shawshank Redemption</Table.Cell>
|
898
1559
|
<Table.Cell>1994</Table.Cell>
|
899
1560
|
<CustomTableCell>9.3</CustomTableCell>
|
900
1561
|
</Table.Row>
|
901
|
-
|
1562
|
+
)
|
1563
|
+
}
|
902
1564
|
}
|
903
|
-
}
|
904
1565
|
|
905
|
-
class Example extends React.Component {
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
1566
|
+
class Example extends React.Component {
|
1567
|
+
state = {
|
1568
|
+
layout: 'auto',
|
1569
|
+
hover: false
|
1570
|
+
}
|
910
1571
|
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
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
|
+
}
|
915
1639
|
}
|
916
1640
|
|
917
|
-
|
918
|
-
|
1641
|
+
render(<Example />)
|
1642
|
+
```
|
919
1643
|
|
920
|
-
|
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 = () => (
|
921
1671
|
<Flex alignItems="start">
|
922
1672
|
<Flex.Item margin="small">
|
923
1673
|
<RadioInputGroup
|
924
1674
|
name="layout2"
|
925
1675
|
description="layout2"
|
926
1676
|
value={layout}
|
927
|
-
onChange={(e, value) =>
|
1677
|
+
onChange={(e, value) => handleChange('layout', value)}
|
928
1678
|
>
|
929
1679
|
<RadioInput label="auto" value="auto" />
|
930
1680
|
<RadioInput label="fixed" value="fixed" />
|
@@ -935,24 +1685,16 @@ class Example extends React.Component {
|
|
935
1685
|
<Checkbox
|
936
1686
|
label="hover"
|
937
1687
|
checked={hover}
|
938
|
-
onChange={(e, value) =>
|
1688
|
+
onChange={(e, value) => handleChange('hover', !hover)}
|
939
1689
|
/>
|
940
1690
|
</Flex.Item>
|
941
1691
|
</Flex>
|
942
1692
|
)
|
943
|
-
}
|
944
|
-
|
945
|
-
render() {
|
946
|
-
const { layout, hover } = this.state
|
947
1693
|
|
948
1694
|
return (
|
949
1695
|
<div>
|
950
|
-
{
|
951
|
-
<Table
|
952
|
-
caption='Top rated movies'
|
953
|
-
layout={layout}
|
954
|
-
hover={hover}
|
955
|
-
>
|
1696
|
+
{renderOptions()}
|
1697
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
956
1698
|
<Table.Head>
|
957
1699
|
<Table.Row>
|
958
1700
|
<Table.ColHeader id="Rank">Rank</Table.ColHeader>
|
@@ -962,7 +1704,7 @@ class Example extends React.Component {
|
|
962
1704
|
</Table.Row>
|
963
1705
|
</Table.Head>
|
964
1706
|
<Table.Body>
|
965
|
-
<CustomTableRow/>
|
1707
|
+
<CustomTableRow />
|
966
1708
|
<Table.Row>
|
967
1709
|
<Table.RowHeader>2</Table.RowHeader>
|
968
1710
|
<Table.Cell>The Godfather</Table.Cell>
|
@@ -980,10 +1722,510 @@ class Example extends React.Component {
|
|
980
1722
|
</div>
|
981
1723
|
)
|
982
1724
|
}
|
983
|
-
}
|
984
1725
|
|
985
|
-
render(<Example />)
|
986
|
-
```
|
1726
|
+
render(<Example />)
|
1727
|
+
```
|
1728
|
+
|
1729
|
+
#### Fully custom components
|
1730
|
+
|
1731
|
+
If you want to use fully custom components you have to pay attention to the following:
|
1732
|
+
|
1733
|
+
- Render them as the appropriate HTML Table tags (`tr`, `th`, ...)
|
1734
|
+
- Read the `hover` prop from `TableContext` to customize hover behaviour
|
1735
|
+
- A11y: Row header cells must have the `scope='row'` HTML attribute
|
1736
|
+
- A11y: Column header cells must have the `scope='col'` and `aria-sort` (if sortable) HTML attribute
|
1737
|
+
|
1738
|
+
Basic fully custom table:
|
1739
|
+
|
1740
|
+
- ```javascript
|
1741
|
+
class CustomTableCell extends React.Component {
|
1742
|
+
render() {
|
1743
|
+
return <td>{this.props.children}</td>
|
1744
|
+
}
|
1745
|
+
}
|
1746
|
+
|
1747
|
+
class CustomTableRow extends React.Component {
|
1748
|
+
static contextType = TableContext
|
1749
|
+
state = { isHovered: false }
|
1750
|
+
|
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
|
+
}
|
1773
|
+
}
|
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
|
+
})
|
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
|
+
|
1866
|
+
return (
|
1867
|
+
<tr
|
1868
|
+
style={rowStyle}
|
1869
|
+
onMouseOver={() => setIsHovered(true)}
|
1870
|
+
onMouseOut={() => setIsHovered(false)}
|
1871
|
+
>
|
1872
|
+
{children}
|
1873
|
+
</tr>
|
1874
|
+
)
|
1875
|
+
}
|
1876
|
+
|
1877
|
+
const Example = () => {
|
1878
|
+
const [layout, setLayout] = useState('auto')
|
1879
|
+
const [hover, setHover] = useState(false)
|
1880
|
+
|
1881
|
+
const handleChange = (field, value) => {
|
1882
|
+
if (field === 'layout') {
|
1883
|
+
setLayout(value)
|
1884
|
+
} else if (field === 'hover') {
|
1885
|
+
setHover(!hover)
|
1886
|
+
}
|
1887
|
+
}
|
1888
|
+
|
1889
|
+
const renderOptions = () => (
|
1890
|
+
<Flex alignItems="start">
|
1891
|
+
<Flex.Item margin="small">
|
1892
|
+
<RadioInputGroup
|
1893
|
+
name="Layout"
|
1894
|
+
description="Layout"
|
1895
|
+
value={layout}
|
1896
|
+
onChange={(e, value) => handleChange('layout', value)}
|
1897
|
+
>
|
1898
|
+
<RadioInput label="auto" value="auto" />
|
1899
|
+
<RadioInput label="fixed" value="fixed" />
|
1900
|
+
</RadioInputGroup>
|
1901
|
+
</Flex.Item>
|
1902
|
+
<Flex.Item margin="small">
|
1903
|
+
<Checkbox
|
1904
|
+
label="hover"
|
1905
|
+
checked={hover}
|
1906
|
+
onChange={(e, value) => handleChange('hover', !hover)}
|
1907
|
+
/>
|
1908
|
+
</Flex.Item>
|
1909
|
+
</Flex>
|
1910
|
+
)
|
1911
|
+
|
1912
|
+
return (
|
1913
|
+
<div>
|
1914
|
+
{renderOptions()}
|
1915
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
1916
|
+
<Table.Head>
|
1917
|
+
<CustomTableRow>
|
1918
|
+
<CustomTableCell scope="col">Rank</CustomTableCell>
|
1919
|
+
<CustomTableCell scope="col">Title</CustomTableCell>
|
1920
|
+
<CustomTableCell scope="col">Year</CustomTableCell>
|
1921
|
+
<CustomTableCell scope="col">Rating</CustomTableCell>
|
1922
|
+
</CustomTableRow>
|
1923
|
+
</Table.Head>
|
1924
|
+
<Table.Body>
|
1925
|
+
<CustomTableRow>
|
1926
|
+
<CustomTableCell scope="row">1</CustomTableCell>
|
1927
|
+
<CustomTableCell>The Godfather</CustomTableCell>
|
1928
|
+
<CustomTableCell>1972</CustomTableCell>
|
1929
|
+
<CustomTableCell>9.2</CustomTableCell>
|
1930
|
+
</CustomTableRow>
|
1931
|
+
<CustomTableRow>
|
1932
|
+
<CustomTableCell scope="row">2</CustomTableCell>
|
1933
|
+
<CustomTableCell>The Godfather: Part II</CustomTableCell>
|
1934
|
+
<CustomTableCell>1974</CustomTableCell>
|
1935
|
+
<CustomTableCell>9.0</CustomTableCell>
|
1936
|
+
</CustomTableRow>
|
1937
|
+
</Table.Body>
|
1938
|
+
</Table>
|
1939
|
+
</div>
|
1940
|
+
)
|
1941
|
+
}
|
1942
|
+
|
1943
|
+
render(<Example />)
|
1944
|
+
```
|
1945
|
+
|
1946
|
+
#### Fully custom components with `stacked` layout
|
1947
|
+
|
1948
|
+
This layout for small screens displays the table as a list. To accomplish this the headers are passed down to cells (in `TableContext`), so they can display what column they are rendering.
|
1949
|
+
In this layout for accessibility not render HTML table tags, just plain DOM elements (e.g. `div`) and use the appropriate ARIA role to signify that it's actually a `Table` (e.g. [`cell`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/cell_role), [`row`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/row_role), [`rowheader`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/rowheader_role)).
|
1950
|
+
Also you need the following props on the components:
|
1951
|
+
|
1952
|
+
##### Table rows
|
1953
|
+
|
1954
|
+
- It should read the `headers` array from `TableContext` and pass its nth element to its nth child (if they have such prop).
|
1955
|
+
|
1956
|
+
##### The children of the first row in `Table.Head` (`Table.ColHeader` by default)
|
1957
|
+
|
1958
|
+
- If the table is sortable the Table needs `id`, `onRequestSort`, `sortDirection` and `stackedSortByLabel` props to render a `Select` to choose how to sort the `Table` (see the props of `Table.ColHeader` for types)
|
1959
|
+
|
1960
|
+
##### Table cells
|
1961
|
+
|
1962
|
+
- It needs to have an optional `header` prop and should display its value so the user knows which column the cell's value belongs to (you can read whether the table is using `stacked` layout from `TableContext`.
|
1963
|
+
|
1964
|
+
Custom table with `stacked` layout support:
|
1965
|
+
|
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
|
+
}
|
2042
|
+
|
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
|
+
}
|
2070
|
+
|
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)
|
2112
|
+
if (isStacked) {
|
2113
|
+
let headerTxt
|
2114
|
+
if (typeof header === 'function') {
|
2115
|
+
headerTxt = React.createElement(header)
|
2116
|
+
} else {
|
2117
|
+
headerTxt = header
|
2118
|
+
}
|
2119
|
+
return (
|
2120
|
+
<div role="cell">
|
2121
|
+
{headerTxt && headerTxt}
|
2122
|
+
{headerTxt && ': '}
|
2123
|
+
{children}
|
2124
|
+
</div>
|
2125
|
+
)
|
2126
|
+
}
|
2127
|
+
return <td>{children}</td>
|
2128
|
+
}
|
2129
|
+
|
2130
|
+
const CustomTableRow = ({ children }) => {
|
2131
|
+
const { hover, headers, isStacked } = useContext(TableContext)
|
2132
|
+
const [isHovered, setIsHovered] = useState(false)
|
2133
|
+
|
2134
|
+
const Tag = isStacked ? 'div' : 'tr'
|
2135
|
+
const rowStyle =
|
2136
|
+
hover && isHovered
|
2137
|
+
? { backgroundColor: 'SeaGreen' }
|
2138
|
+
: { backgroundColor: 'white' }
|
2139
|
+
|
2140
|
+
return (
|
2141
|
+
<Tag
|
2142
|
+
style={rowStyle}
|
2143
|
+
role={isStacked ? 'row' : undefined}
|
2144
|
+
onMouseOver={() => setIsHovered(true)}
|
2145
|
+
onMouseOut={() => setIsHovered(false)}
|
2146
|
+
>
|
2147
|
+
{React.Children.toArray(children)
|
2148
|
+
.filter(React.isValidElement)
|
2149
|
+
.map((child, index) => {
|
2150
|
+
return React.cloneElement(child, {
|
2151
|
+
key: child.props.name,
|
2152
|
+
// used by `CustomTableCell` to render its column title in `stacked` layout
|
2153
|
+
header: headers && headers[index]
|
2154
|
+
})
|
2155
|
+
})}
|
2156
|
+
</Tag>
|
2157
|
+
)
|
2158
|
+
}
|
2159
|
+
|
2160
|
+
const Example = () => {
|
2161
|
+
const [layout, setLayout] = useState('auto')
|
2162
|
+
const [hover, setHover] = useState(false)
|
2163
|
+
|
2164
|
+
const handleChange = (field, value) => {
|
2165
|
+
if (field === 'layout') {
|
2166
|
+
setLayout(value)
|
2167
|
+
} else if (field === 'hover') {
|
2168
|
+
setHover(!hover)
|
2169
|
+
}
|
2170
|
+
}
|
2171
|
+
|
2172
|
+
const renderOptions = () => (
|
2173
|
+
<Flex alignItems="start">
|
2174
|
+
<Flex.Item margin="small">
|
2175
|
+
<RadioInputGroup
|
2176
|
+
name="customStackedLayout"
|
2177
|
+
description="Layout"
|
2178
|
+
value={layout}
|
2179
|
+
onChange={(e, value) => handleChange('layout', value)}
|
2180
|
+
>
|
2181
|
+
<RadioInput label="auto" value="auto" />
|
2182
|
+
<RadioInput label="fixed" value="fixed" />
|
2183
|
+
<RadioInput label="stacked" value="stacked" />
|
2184
|
+
</RadioInputGroup>
|
2185
|
+
</Flex.Item>
|
2186
|
+
<Flex.Item margin="small">
|
2187
|
+
<Checkbox
|
2188
|
+
label="hover"
|
2189
|
+
checked={hover}
|
2190
|
+
onChange={(e, value) => handleChange('hover', !hover)}
|
2191
|
+
/>
|
2192
|
+
</Flex.Item>
|
2193
|
+
</Flex>
|
2194
|
+
)
|
2195
|
+
|
2196
|
+
return (
|
2197
|
+
<div>
|
2198
|
+
{renderOptions()}
|
2199
|
+
<Table caption="Top rated movies" layout={layout} hover={hover}>
|
2200
|
+
<Table.Head>
|
2201
|
+
<CustomTableRow>
|
2202
|
+
<CustomTableCell scope="col">Rank</CustomTableCell>
|
2203
|
+
<CustomTableCell scope="col">Title</CustomTableCell>
|
2204
|
+
<CustomTableCell scope="col">Year</CustomTableCell>
|
2205
|
+
<CustomTableCell scope="col">Rating</CustomTableCell>
|
2206
|
+
</CustomTableRow>
|
2207
|
+
</Table.Head>
|
2208
|
+
<Table.Body>
|
2209
|
+
<CustomTableRow>
|
2210
|
+
<CustomTableCell scope="row">1</CustomTableCell>
|
2211
|
+
<CustomTableCell>The Godfather</CustomTableCell>
|
2212
|
+
<CustomTableCell>1972</CustomTableCell>
|
2213
|
+
<CustomTableCell>9.2</CustomTableCell>
|
2214
|
+
</CustomTableRow>
|
2215
|
+
<CustomTableRow>
|
2216
|
+
<CustomTableCell scope="row">2</CustomTableCell>
|
2217
|
+
<CustomTableCell>The Godfather: Part II</CustomTableCell>
|
2218
|
+
<CustomTableCell>1974</CustomTableCell>
|
2219
|
+
<CustomTableCell>9.0</CustomTableCell>
|
2220
|
+
</CustomTableRow>
|
2221
|
+
</Table.Body>
|
2222
|
+
</Table>
|
2223
|
+
</div>
|
2224
|
+
)
|
2225
|
+
}
|
2226
|
+
|
2227
|
+
render(<Example />)
|
2228
|
+
```
|
987
2229
|
|
988
2230
|
### Guidelines
|
989
2231
|
|